// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/builtins/builtins-promise-gen.h"

#include "src/builtins/builtins-constructor-gen.h"
#include "src/builtins/builtins-iterator-gen.h"
#include "src/builtins/builtins-promise.h"
#include "src/builtins/builtins-utils-gen.h"
#include "src/builtins/builtins.h"
#include "src/code-factory.h"
#include "src/code-stub-assembler.h"
#include "src/objects-inl.h"
#include "src/objects/js-promise.h"
#include "src/objects/smi.h"

namespace v8 {
namespace internal {

    typedef compiler::Node Node;
    template <class T>
    using TNode = CodeStubAssembler::TNode<T>;
    using IteratorRecord = IteratorBuiltinsAssembler::IteratorRecord;

    Node* PromiseBuiltinsAssembler::AllocateJSPromise(Node* context)
    {
        Node* const native_context = LoadNativeContext(context);
        Node* const promise_fun = LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
        CSA_ASSERT(this, IsFunctionWithPrototypeSlotMap(LoadMap(promise_fun)));
        Node* const promise_map = LoadObjectField(promise_fun, JSFunction::kPrototypeOrInitialMapOffset);
        Node* const promise = Allocate(JSPromise::kSizeWithEmbedderFields);
        StoreMapNoWriteBarrier(promise, promise_map);
        StoreObjectFieldRoot(promise, JSPromise::kPropertiesOrHashOffset,
            RootIndex::kEmptyFixedArray);
        StoreObjectFieldRoot(promise, JSPromise::kElementsOffset,
            RootIndex::kEmptyFixedArray);
        return promise;
    }

    void PromiseBuiltinsAssembler::PromiseInit(Node* promise)
    {
        STATIC_ASSERT(v8::Promise::kPending == 0);
        StoreObjectFieldNoWriteBarrier(promise, JSPromise::kReactionsOrResultOffset,
            SmiConstant(Smi::zero()));
        StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset,
            SmiConstant(Smi::zero()));
        for (int offset = JSPromise::kSize;
             offset < JSPromise::kSizeWithEmbedderFields; offset += kTaggedSize) {
            StoreObjectFieldNoWriteBarrier(promise, offset, SmiConstant(Smi::zero()));
        }
    }

    Node* PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Node* context)
    {
        return AllocateAndInitJSPromise(context, UndefinedConstant());
    }

    Node* PromiseBuiltinsAssembler::AllocateAndInitJSPromise(Node* context,
        Node* parent)
    {
        Node* const instance = AllocateJSPromise(context);
        PromiseInit(instance);

        Label out(this);
        GotoIfNot(IsPromiseHookEnabledOrHasAsyncEventDelegate(), &out);
        CallRuntime(Runtime::kPromiseHookInit, context, instance, parent);
        Goto(&out);

        BIND(&out);
        return instance;
    }

    Node* PromiseBuiltinsAssembler::AllocateAndSetJSPromise(
        Node* context, v8::Promise::PromiseState status, Node* result)
    {
        DCHECK_NE(Promise::kPending, status);

        Node* const instance = AllocateJSPromise(context);
        StoreObjectFieldNoWriteBarrier(instance, JSPromise::kReactionsOrResultOffset,
            result);
        STATIC_ASSERT(JSPromise::kStatusShift == 0);
        StoreObjectFieldNoWriteBarrier(instance, JSPromise::kFlagsOffset,
            SmiConstant(status));
        for (int offset = JSPromise::kSize;
             offset < JSPromise::kSizeWithEmbedderFields; offset += kTaggedSize) {
            StoreObjectFieldNoWriteBarrier(instance, offset, SmiConstant(0));
        }

        Label out(this);
        GotoIfNot(IsPromiseHookEnabledOrHasAsyncEventDelegate(), &out);
        CallRuntime(Runtime::kPromiseHookInit, context, instance,
            UndefinedConstant());
        Goto(&out);

        BIND(&out);
        return instance;
    }

    std::pair<Node*, Node*>
    PromiseBuiltinsAssembler::CreatePromiseResolvingFunctions(
        Node* promise, Node* debug_event, Node* native_context)
    {
        Node* const promise_context = CreatePromiseResolvingFunctionsContext(
            promise, debug_event, native_context);
        Node* const map = LoadContextElement(
            native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
        Node* const resolve_info = LoadContextElement(
            native_context,
            Context::PROMISE_CAPABILITY_DEFAULT_RESOLVE_SHARED_FUN_INDEX);
        Node* const resolve = AllocateFunctionWithMapAndContext(map, resolve_info, promise_context);
        Node* const reject_info = LoadContextElement(
            native_context,
            Context::PROMISE_CAPABILITY_DEFAULT_REJECT_SHARED_FUN_INDEX);
        Node* const reject = AllocateFunctionWithMapAndContext(map, reject_info, promise_context);
        return std::make_pair(resolve, reject);
    }

    void PromiseBuiltinsAssembler::ExtractHandlerContext(Node* handler,
        Variable* var_context)
    {
        VARIABLE(var_handler, MachineRepresentation::kTagged, handler);
        Label loop(this, &var_handler), done(this, Label::kDeferred);
        Goto(&loop);
        BIND(&loop);
        {
            Label if_function(this), if_bound_function(this, Label::kDeferred),
                if_proxy(this, Label::kDeferred);
            GotoIf(TaggedIsSmi(var_handler.value()), &done);

            int32_t case_values[] = {
                JS_FUNCTION_TYPE,
                JS_BOUND_FUNCTION_TYPE,
                JS_PROXY_TYPE,
            };
            Label* case_labels[] = {
                &if_function,
                &if_bound_function,
                &if_proxy,
            };
            static_assert(arraysize(case_values) == arraysize(case_labels), "");
            TNode<Map> handler_map = LoadMap(var_handler.value());
            TNode<Int32T> handler_type = LoadMapInstanceType(handler_map);
            Switch(handler_type, &done, case_values, case_labels,
                arraysize(case_labels));

            BIND(&if_bound_function);
            {
                // Use the target function's context for JSBoundFunction.
                var_handler.Bind(LoadObjectField(
                    var_handler.value(), JSBoundFunction::kBoundTargetFunctionOffset));
                Goto(&loop);
            }

            BIND(&if_proxy);
            {
                // Use the target function's context for JSProxy.
                // If the proxy is revoked, |var_handler| will be undefined and this
                // function will return with unchanged |var_context|.
                var_handler.Bind(
                    LoadObjectField(var_handler.value(), JSProxy::kTargetOffset));
                Goto(&loop);
            }

            BIND(&if_function);
            {
                // Use the function's context.
                Node* handler_context = LoadObjectField(var_handler.value(), JSFunction::kContextOffset);
                var_context->Bind(LoadNativeContext(CAST(handler_context)));
                Goto(&done);
            }
        }

        // If no valid context is available, |var_context| is unchanged and the caller
        // will use a fallback context.
        BIND(&done);
    }

    // ES #sec-newpromisecapability
    TF_BUILTIN(NewPromiseCapability, PromiseBuiltinsAssembler)
    {
        Node* const context = Parameter(Descriptor::kContext);
        Node* const constructor = Parameter(Descriptor::kConstructor);
        Node* const debug_event = Parameter(Descriptor::kDebugEvent);
        TNode<Context> const native_context = LoadNativeContext(context);

        Label if_not_constructor(this, Label::kDeferred),
            if_notcallable(this, Label::kDeferred), if_fast_promise_capability(this),
            if_slow_promise_capability(this, Label::kDeferred);
        GotoIf(TaggedIsSmi(constructor), &if_not_constructor);
        GotoIfNot(IsConstructorMap(LoadMap(constructor)), &if_not_constructor);
        Branch(WordEqual(constructor,
                   LoadContextElement(native_context,
                       Context::PROMISE_FUNCTION_INDEX)),
            &if_fast_promise_capability, &if_slow_promise_capability);

        BIND(&if_fast_promise_capability);
        {
            Node* promise = AllocateAndInitJSPromise(native_context, UndefinedConstant());

            Node* resolve = nullptr;
            Node* reject = nullptr;
            std::tie(resolve, reject) = CreatePromiseResolvingFunctions(promise, debug_event, native_context);

            Node* capability = Allocate(PromiseCapability::kSize);
            StoreMapNoWriteBarrier(capability, RootIndex::kPromiseCapabilityMap);
            StoreObjectFieldNoWriteBarrier(capability,
                PromiseCapability::kPromiseOffset, promise);
            StoreObjectFieldNoWriteBarrier(capability,
                PromiseCapability::kResolveOffset, resolve);
            StoreObjectFieldNoWriteBarrier(capability, PromiseCapability::kRejectOffset,
                reject);
            Return(capability);
        }

        BIND(&if_slow_promise_capability);
        {
            Node* capability = Allocate(PromiseCapability::kSize);
            StoreMapNoWriteBarrier(capability, RootIndex::kPromiseCapabilityMap);
            StoreObjectFieldRoot(capability, PromiseCapability::kPromiseOffset,
                RootIndex::kUndefinedValue);
            StoreObjectFieldRoot(capability, PromiseCapability::kResolveOffset,
                RootIndex::kUndefinedValue);
            StoreObjectFieldRoot(capability, PromiseCapability::kRejectOffset,
                RootIndex::kUndefinedValue);

            Node* executor_context = CreatePromiseGetCapabilitiesExecutorContext(capability, native_context);
            Node* executor_info = LoadContextElement(
                native_context, Context::PROMISE_GET_CAPABILITIES_EXECUTOR_SHARED_FUN);
            Node* function_map = LoadContextElement(
                native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
            TNode<JSFunction> executor = CAST(AllocateFunctionWithMapAndContext(
                function_map, executor_info, executor_context));

            Node* promise = Construct(native_context, CAST(constructor), executor);
            StoreObjectField(capability, PromiseCapability::kPromiseOffset, promise);

            Node* resolve = LoadObjectField(capability, PromiseCapability::kResolveOffset);
            GotoIf(TaggedIsSmi(resolve), &if_notcallable);
            GotoIfNot(IsCallable(resolve), &if_notcallable);

            Node* reject = LoadObjectField(capability, PromiseCapability::kRejectOffset);
            GotoIf(TaggedIsSmi(reject), &if_notcallable);
            GotoIfNot(IsCallable(reject), &if_notcallable);
            Return(capability);
        }

        BIND(&if_not_constructor);
        ThrowTypeError(context, MessageTemplate::kNotConstructor, constructor);

        BIND(&if_notcallable);
        ThrowTypeError(context, MessageTemplate::kPromiseNonCallable);
    }

    Node* PromiseBuiltinsAssembler::CreatePromiseContext(Node* native_context,
        int slots)
    {
        DCHECK_GE(slots, Context::MIN_CONTEXT_SLOTS);

        Node* const context = AllocateInNewSpace(FixedArray::SizeFor(slots));
        InitializeFunctionContext(native_context, context, slots);
        return context;
    }

    Node* PromiseBuiltinsAssembler::CreatePromiseAllResolveElementContext(
        Node* promise_capability, Node* native_context)
    {
        CSA_ASSERT(this, IsNativeContext(native_context));

        // TODO(bmeurer): Manually fold this into a single allocation.
        TNode<Map> array_map = CAST(LoadContextElement(
            native_context, Context::JS_ARRAY_PACKED_ELEMENTS_MAP_INDEX));
        TNode<JSArray> values_array = AllocateJSArray(
            PACKED_ELEMENTS, array_map, IntPtrConstant(0), SmiConstant(0));

        Node* const context = CreatePromiseContext(
            native_context, PromiseBuiltins::kPromiseAllResolveElementLength);
        StoreContextElementNoWriteBarrier(
            context, PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
            SmiConstant(1));
        StoreContextElementNoWriteBarrier(
            context, PromiseBuiltins::kPromiseAllResolveElementCapabilitySlot,
            promise_capability);
        StoreContextElementNoWriteBarrier(
            context, PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot,
            values_array);

        return context;
    }

    TNode<JSFunction>
    PromiseBuiltinsAssembler::CreatePromiseAllResolveElementFunction(
        Node* context, TNode<Smi> index, Node* native_context, int slot_index)
    {
        CSA_ASSERT(this, SmiGreaterThan(index, SmiConstant(0)));
        CSA_ASSERT(this, SmiLessThanOrEqual(index, SmiConstant(PropertyArray::HashField::kMax)));
        CSA_ASSERT(this, IsNativeContext(native_context));

        Node* const map = LoadContextElement(
            native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
        Node* const resolve_info = LoadContextElement(native_context, slot_index);
        TNode<JSFunction> resolve = Cast(AllocateFunctionWithMapAndContext(map, resolve_info, context));

        STATIC_ASSERT(PropertyArray::kNoHashSentinel == 0);
        StoreObjectFieldNoWriteBarrier(resolve, JSFunction::kPropertiesOrHashOffset,
            index);

        return resolve;
    }

    Node* PromiseBuiltinsAssembler::CreatePromiseResolvingFunctionsContext(
        Node* promise, Node* debug_event, Node* native_context)
    {
        Node* const context = CreatePromiseContext(
            native_context, PromiseBuiltins::kPromiseContextLength);
        StoreContextElementNoWriteBarrier(context, PromiseBuiltins::kPromiseSlot,
            promise);
        StoreContextElementNoWriteBarrier(
            context, PromiseBuiltins::kAlreadyResolvedSlot, FalseConstant());
        StoreContextElementNoWriteBarrier(context, PromiseBuiltins::kDebugEventSlot,
            debug_event);
        return context;
    }

    Node* PromiseBuiltinsAssembler::CreatePromiseGetCapabilitiesExecutorContext(
        Node* promise_capability, Node* native_context)
    {
        int kContextLength = PromiseBuiltins::kCapabilitiesContextLength;
        Node* context = CreatePromiseContext(native_context, kContextLength);
        StoreContextElementNoWriteBarrier(context, PromiseBuiltins::kCapabilitySlot,
            promise_capability);
        return context;
    }

    Node* PromiseBuiltinsAssembler::PromiseHasHandler(Node* promise)
    {
        Node* const flags = LoadObjectField(promise, JSPromise::kFlagsOffset);
        return IsSetWord(SmiUntag(flags), 1 << JSPromise::kHasHandlerBit);
    }

    void PromiseBuiltinsAssembler::PromiseSetHasHandler(Node* promise)
    {
        TNode<Smi> const flags = CAST(LoadObjectField(promise, JSPromise::kFlagsOffset));
        TNode<Smi> const new_flags = SmiOr(flags, SmiConstant(1 << JSPromise::kHasHandlerBit));
        StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags);
    }

    Node* PromiseBuiltinsAssembler::IsPromiseStatus(
        Node* actual, v8::Promise::PromiseState expected)
    {
        return Word32Equal(actual, Int32Constant(expected));
    }

    Node* PromiseBuiltinsAssembler::PromiseStatus(Node* promise)
    {
        STATIC_ASSERT(JSPromise::kStatusShift == 0);
        TNode<Smi> const flags = CAST(LoadObjectField(promise, JSPromise::kFlagsOffset));
        return Word32And(SmiToInt32(flags), Int32Constant(JSPromise::kStatusMask));
    }

    void PromiseBuiltinsAssembler::PromiseSetStatus(
        Node* promise, v8::Promise::PromiseState const status)
    {
        CSA_ASSERT(this,
            IsPromiseStatus(PromiseStatus(promise), v8::Promise::kPending));
        CHECK_NE(status, v8::Promise::kPending);

        TNode<Smi> mask = SmiConstant(status);
        TNode<Smi> const flags = CAST(LoadObjectField(promise, JSPromise::kFlagsOffset));
        StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset,
            SmiOr(flags, mask));
    }

    void PromiseBuiltinsAssembler::PromiseSetHandledHint(Node* promise)
    {
        TNode<Smi> const flags = CAST(LoadObjectField(promise, JSPromise::kFlagsOffset));
        TNode<Smi> const new_flags = SmiOr(flags, SmiConstant(1 << JSPromise::kHandledHintBit));
        StoreObjectFieldNoWriteBarrier(promise, JSPromise::kFlagsOffset, new_flags);
    }

    // ES #sec-performpromisethen
    void PromiseBuiltinsAssembler::PerformPromiseThen(
        Node* context, Node* promise, Node* on_fulfilled, Node* on_rejected,
        Node* result_promise_or_capability)
    {
        CSA_ASSERT(this, TaggedIsNotSmi(promise));
        CSA_ASSERT(this, IsJSPromise(promise));
        CSA_ASSERT(this,
            Word32Or(IsCallable(on_fulfilled), IsUndefined(on_fulfilled)));
        CSA_ASSERT(this, Word32Or(IsCallable(on_rejected), IsUndefined(on_rejected)));
        CSA_ASSERT(this, TaggedIsNotSmi(result_promise_or_capability));
        CSA_ASSERT(
            this,
            Word32Or(Word32Or(IsJSPromise(result_promise_or_capability),
                         IsPromiseCapability(result_promise_or_capability)),
                IsUndefined(result_promise_or_capability)));

        Label if_pending(this), if_notpending(this), done(this);
        Node* const status = PromiseStatus(promise);
        Branch(IsPromiseStatus(status, v8::Promise::kPending), &if_pending,
            &if_notpending);

        BIND(&if_pending);
        {
            // The {promise} is still in "Pending" state, so we just record a new
            // PromiseReaction holding both the onFulfilled and onRejected callbacks.
            // Once the {promise} is resolved we decide on the concrete handler to
            // push onto the microtask queue.
            Node* const promise_reactions = LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
            Node* const reaction = AllocatePromiseReaction(promise_reactions, result_promise_or_capability,
                on_fulfilled, on_rejected);
            StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reaction);
            Goto(&done);
        }

        BIND(&if_notpending);
        {
            VARIABLE(var_map, MachineRepresentation::kTagged);
            VARIABLE(var_handler, MachineRepresentation::kTagged);
            VARIABLE(var_handler_context, MachineRepresentation::kTagged,
                UndefinedConstant());
            Label if_fulfilled(this), if_rejected(this, Label::kDeferred),
                enqueue(this);
            Branch(IsPromiseStatus(status, v8::Promise::kFulfilled), &if_fulfilled,
                &if_rejected);

            BIND(&if_fulfilled);
            {
                var_map.Bind(LoadRoot(RootIndex::kPromiseFulfillReactionJobTaskMap));
                var_handler.Bind(on_fulfilled);

                Label use_fallback(this, Label::kDeferred), done(this);
                ExtractHandlerContext(on_fulfilled, &var_handler_context);
                Branch(IsUndefined(var_handler_context.value()), &use_fallback, &done);

                BIND(&use_fallback);
                var_handler_context.Bind(context);
                ExtractHandlerContext(on_rejected, &var_handler_context);
                Goto(&done);

                BIND(&done);
                Goto(&enqueue);
            }

            BIND(&if_rejected);
            {
                CSA_ASSERT(this, IsPromiseStatus(status, v8::Promise::kRejected));
                var_map.Bind(LoadRoot(RootIndex::kPromiseRejectReactionJobTaskMap));
                var_handler.Bind(on_rejected);

                Label use_fallback(this, Label::kDeferred), done(this);
                ExtractHandlerContext(on_rejected, &var_handler_context);
                Branch(IsUndefined(var_handler_context.value()), &use_fallback, &done);

                BIND(&use_fallback);
                var_handler_context.Bind(context);
                ExtractHandlerContext(on_fulfilled, &var_handler_context);
                Goto(&done);
                BIND(&done);

                GotoIf(PromiseHasHandler(promise), &enqueue);
                CallRuntime(Runtime::kPromiseRevokeReject, context, promise);
                Goto(&enqueue);
            }

            BIND(&enqueue);
            {
                Node* argument = LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);
                Node* microtask = AllocatePromiseReactionJobTask(
                    var_map.value(), var_handler_context.value(), argument,
                    var_handler.value(), result_promise_or_capability);
                CallBuiltin(Builtins::kEnqueueMicrotask, var_handler_context.value(),
                    microtask);
                Goto(&done);
            }
        }

        BIND(&done);
        PromiseSetHasHandler(promise);
    }

    // ES #sec-performpromisethen
    TF_BUILTIN(PerformPromiseThen, PromiseBuiltinsAssembler)
    {
        Node* const context = Parameter(Descriptor::kContext);
        Node* const promise = Parameter(Descriptor::kPromise);
        Node* const on_fulfilled = Parameter(Descriptor::kOnFulfilled);
        Node* const on_rejected = Parameter(Descriptor::kOnRejected);
        Node* const result_promise = Parameter(Descriptor::kResultPromise);

        CSA_ASSERT(this, TaggedIsNotSmi(result_promise));
        CSA_ASSERT(
            this, Word32Or(IsJSPromise(result_promise), IsUndefined(result_promise)));

        PerformPromiseThen(context, promise, on_fulfilled, on_rejected,
            result_promise);
        Return(result_promise);
    }

    Node* PromiseBuiltinsAssembler::AllocatePromiseReaction(
        Node* next, Node* promise_or_capability, Node* fulfill_handler,
        Node* reject_handler)
    {
        Node* const reaction = Allocate(PromiseReaction::kSize);
        StoreMapNoWriteBarrier(reaction, RootIndex::kPromiseReactionMap);
        StoreObjectFieldNoWriteBarrier(reaction, PromiseReaction::kNextOffset, next);
        StoreObjectFieldNoWriteBarrier(reaction,
            PromiseReaction::kPromiseOrCapabilityOffset,
            promise_or_capability);
        StoreObjectFieldNoWriteBarrier(
            reaction, PromiseReaction::kFulfillHandlerOffset, fulfill_handler);
        StoreObjectFieldNoWriteBarrier(
            reaction, PromiseReaction::kRejectHandlerOffset, reject_handler);
        return reaction;
    }

    Node* PromiseBuiltinsAssembler::AllocatePromiseReactionJobTask(
        Node* map, Node* context, Node* argument, Node* handler,
        Node* promise_or_capability)
    {
        Node* const microtask = Allocate(PromiseReactionJobTask::kSize);
        StoreMapNoWriteBarrier(microtask, map);
        StoreObjectFieldNoWriteBarrier(
            microtask, PromiseReactionJobTask::kArgumentOffset, argument);
        StoreObjectFieldNoWriteBarrier(
            microtask, PromiseReactionJobTask::kContextOffset, context);
        StoreObjectFieldNoWriteBarrier(
            microtask, PromiseReactionJobTask::kHandlerOffset, handler);
        StoreObjectFieldNoWriteBarrier(
            microtask, PromiseReactionJobTask::kPromiseOrCapabilityOffset,
            promise_or_capability);
        return microtask;
    }

    Node* PromiseBuiltinsAssembler::AllocatePromiseReactionJobTask(
        RootIndex map_root_index, Node* context, Node* argument, Node* handler,
        Node* promise_or_capability)
    {
        DCHECK(map_root_index == RootIndex::kPromiseFulfillReactionJobTaskMap || map_root_index == RootIndex::kPromiseRejectReactionJobTaskMap);
        Node* const map = LoadRoot(map_root_index);
        return AllocatePromiseReactionJobTask(map, context, argument, handler,
            promise_or_capability);
    }

    Node* PromiseBuiltinsAssembler::AllocatePromiseResolveThenableJobTask(
        Node* promise_to_resolve, Node* then, Node* thenable, Node* context)
    {
        Node* const microtask = Allocate(PromiseResolveThenableJobTask::kSize);
        StoreMapNoWriteBarrier(microtask,
            RootIndex::kPromiseResolveThenableJobTaskMap);
        StoreObjectFieldNoWriteBarrier(
            microtask, PromiseResolveThenableJobTask::kContextOffset, context);
        StoreObjectFieldNoWriteBarrier(
            microtask, PromiseResolveThenableJobTask::kPromiseToResolveOffset,
            promise_to_resolve);
        StoreObjectFieldNoWriteBarrier(
            microtask, PromiseResolveThenableJobTask::kThenOffset, then);
        StoreObjectFieldNoWriteBarrier(
            microtask, PromiseResolveThenableJobTask::kThenableOffset, thenable);
        return microtask;
    }

    // ES #sec-triggerpromisereactions
    Node* PromiseBuiltinsAssembler::TriggerPromiseReactions(
        Node* context, Node* reactions, Node* argument,
        PromiseReaction::Type type)
    {
        // We need to reverse the {reactions} here, since we record them on the
        // JSPromise in the reverse order.
        {
            VARIABLE(var_current, MachineRepresentation::kTagged, reactions);
            VARIABLE(var_reversed, MachineRepresentation::kTagged,
                SmiConstant(Smi::zero()));

            // As an additional safety net against misuse of the V8 Extras API, we
            // sanity check the {reactions} to make sure that they are actually
            // PromiseReaction instances and not actual JavaScript values (which
            // would indicate that we're rejecting or resolving an already settled
            // promise), see https://crbug.com/931640 for details on this.
            TNode<Map> promise_reaction_map = CAST(LoadRoot(RootIndex::kPromiseReactionMap));

            Label loop(this, { &var_current, &var_reversed }), done_loop(this);
            Goto(&loop);
            BIND(&loop);
            {
                Node* current = var_current.value();
                GotoIf(TaggedIsSmi(current), &done_loop);
                CSA_CHECK(this, WordEqual(LoadMap(CAST(current)), promise_reaction_map));
                var_current.Bind(LoadObjectField(current, PromiseReaction::kNextOffset));
                StoreObjectField(current, PromiseReaction::kNextOffset,
                    var_reversed.value());
                var_reversed.Bind(current);
                Goto(&loop);
            }
            BIND(&done_loop);
            reactions = var_reversed.value();
        }

        // Morph the {reactions} into PromiseReactionJobTasks and push them
        // onto the microtask queue.
        {
            VARIABLE(var_current, MachineRepresentation::kTagged, reactions);

            Label loop(this, { &var_current }), done_loop(this);
            Goto(&loop);
            BIND(&loop);
            {
                Node* current = var_current.value();
                GotoIf(TaggedIsSmi(current), &done_loop);
                var_current.Bind(LoadObjectField(current, PromiseReaction::kNextOffset));

                VARIABLE(var_context, MachineRepresentation::kTagged,
                    UndefinedConstant());

                Node* primary_handler;
                Node* secondary_handler;
                if (type == PromiseReaction::kFulfill) {
                    primary_handler = LoadObjectField(current, PromiseReaction::kFulfillHandlerOffset);
                    secondary_handler = LoadObjectField(current, PromiseReaction::kRejectHandlerOffset);
                } else {
                    primary_handler = LoadObjectField(current, PromiseReaction::kRejectHandlerOffset);
                    secondary_handler = LoadObjectField(current, PromiseReaction::kFulfillHandlerOffset);
                }

                {
                    Label use_fallback(this, Label::kDeferred), done(this);
                    ExtractHandlerContext(primary_handler, &var_context);
                    Branch(IsUndefined(var_context.value()), &use_fallback, &done);

                    BIND(&use_fallback);
                    var_context.Bind(context);
                    ExtractHandlerContext(secondary_handler, &var_context);
                    CSA_ASSERT(this, IsNotUndefined(var_context.value()));
                    Goto(&done);

                    BIND(&done);
                }

                // Morph {current} from a PromiseReaction into a PromiseReactionJobTask
                // and schedule that on the microtask queue. We try to minimize the number
                // of stores here to avoid screwing up the store buffer.
                STATIC_ASSERT(static_cast<int>(PromiseReaction::kSize) == static_cast<int>(PromiseReactionJobTask::kSize));
                if (type == PromiseReaction::kFulfill) {
                    StoreMapNoWriteBarrier(current,
                        RootIndex::kPromiseFulfillReactionJobTaskMap);
                    StoreObjectField(current, PromiseReactionJobTask::kArgumentOffset,
                        argument);
                    StoreObjectField(current, PromiseReactionJobTask::kContextOffset,
                        var_context.value());
                    STATIC_ASSERT(
                        static_cast<int>(PromiseReaction::kFulfillHandlerOffset) == static_cast<int>(PromiseReactionJobTask::kHandlerOffset));
                    STATIC_ASSERT(
                        static_cast<int>(PromiseReaction::kPromiseOrCapabilityOffset) == static_cast<int>(PromiseReactionJobTask::kPromiseOrCapabilityOffset));
                } else {
                    StoreMapNoWriteBarrier(current,
                        RootIndex::kPromiseRejectReactionJobTaskMap);
                    StoreObjectField(current, PromiseReactionJobTask::kArgumentOffset,
                        argument);
                    StoreObjectField(current, PromiseReactionJobTask::kContextOffset,
                        var_context.value());
                    StoreObjectField(current, PromiseReactionJobTask::kHandlerOffset,
                        primary_handler);
                    STATIC_ASSERT(
                        static_cast<int>(PromiseReaction::kPromiseOrCapabilityOffset) == static_cast<int>(PromiseReactionJobTask::kPromiseOrCapabilityOffset));
                }
                CallBuiltin(Builtins::kEnqueueMicrotask, var_context.value(), current);
                Goto(&loop);
            }
            BIND(&done_loop);
        }

        return UndefinedConstant();
    }

    template <typename... TArgs>
    Node* PromiseBuiltinsAssembler::InvokeThen(Node* native_context, Node* receiver,
        TArgs... args)
    {
        CSA_ASSERT(this, IsNativeContext(native_context));

        VARIABLE(var_result, MachineRepresentation::kTagged);
        Label if_fast(this), if_slow(this, Label::kDeferred), done(this, &var_result);
        GotoIf(TaggedIsSmi(receiver), &if_slow);
        Node* const receiver_map = LoadMap(receiver);
        // We can skip the "then" lookup on {receiver} if it's [[Prototype]]
        // is the (initial) Promise.prototype and the Promise#then protector
        // is intact, as that guards the lookup path for the "then" property
        // on JSPromise instances which have the (initial) %PromisePrototype%.
        BranchIfPromiseThenLookupChainIntact(native_context, receiver_map, &if_fast,
            &if_slow);

        BIND(&if_fast);
        {
            Node* const then = LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
            Node* const result = CallJS(CodeFactory::CallFunction(
                                            isolate(), ConvertReceiverMode::kNotNullOrUndefined),
                native_context, then, receiver, args...);
            var_result.Bind(result);
            Goto(&done);
        }

        BIND(&if_slow);
        {
            Node* const then = GetProperty(native_context, receiver,
                isolate()->factory()->then_string());
            Node* const result = CallJS(
                CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
                native_context, then, receiver, args...);
            var_result.Bind(result);
            Goto(&done);
        }

        BIND(&done);
        return var_result.value();
    }

    Node* PromiseBuiltinsAssembler::InvokeResolve(Node* native_context,
        Node* constructor, Node* value,
        Label* if_exception,
        Variable* var_exception)
    {
        CSA_ASSERT(this, IsNativeContext(native_context));

        VARIABLE(var_result, MachineRepresentation::kTagged);
        Label if_fast(this), if_slow(this, Label::kDeferred), done(this, &var_result);
        // We can skip the "resolve" lookup on {constructor} if it's the
        // Promise constructor and the Promise.resolve protector is intact,
        // as that guards the lookup path for the "resolve" property on the
        // Promise constructor.
        BranchIfPromiseResolveLookupChainIntact(native_context, constructor, &if_fast,
            &if_slow);

        BIND(&if_fast);
        {
            Node* const result = CallBuiltin(Builtins::kPromiseResolve, native_context,
                constructor, value);
            GotoIfException(result, if_exception, var_exception);

            var_result.Bind(result);
            Goto(&done);
        }

        BIND(&if_slow);
        {
            Node* const resolve = GetProperty(native_context, constructor, factory()->resolve_string());
            GotoIfException(resolve, if_exception, var_exception);

            Node* const result = CallJS(
                CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
                native_context, resolve, constructor, value);
            GotoIfException(result, if_exception, var_exception);

            var_result.Bind(result);
            Goto(&done);
        }

        BIND(&done);
        return var_result.value();
    }

    void PromiseBuiltinsAssembler::BranchIfPromiseResolveLookupChainIntact(
        Node* native_context, Node* constructor, Label* if_fast, Label* if_slow)
    {
        CSA_ASSERT(this, IsNativeContext(native_context));

        GotoIfForceSlowPath(if_slow);
        Node* const promise_fun = LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
        GotoIfNot(WordEqual(promise_fun, constructor), if_slow);
        Branch(IsPromiseResolveProtectorCellInvalid(), if_slow, if_fast);
    }

    void PromiseBuiltinsAssembler::GotoIfNotPromiseResolveLookupChainIntact(
        Node* native_context, Node* constructor, Label* if_slow)
    {
        Label if_fast(this);
        BranchIfPromiseResolveLookupChainIntact(native_context, constructor, &if_fast,
            if_slow);
        BIND(&if_fast);
    }

    void PromiseBuiltinsAssembler::BranchIfPromiseSpeciesLookupChainIntact(
        Node* native_context, Node* promise_map, Label* if_fast, Label* if_slow)
    {
        CSA_ASSERT(this, IsNativeContext(native_context));
        CSA_ASSERT(this, IsJSPromiseMap(promise_map));

        Node* const promise_prototype = LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
        GotoIfForceSlowPath(if_slow);
        GotoIfNot(WordEqual(LoadMapPrototype(promise_map), promise_prototype),
            if_slow);
        Branch(IsPromiseSpeciesProtectorCellInvalid(), if_slow, if_fast);
    }

    void PromiseBuiltinsAssembler::BranchIfPromiseThenLookupChainIntact(
        Node* native_context, Node* receiver_map, Label* if_fast, Label* if_slow)
    {
        CSA_ASSERT(this, IsMap(receiver_map));
        CSA_ASSERT(this, IsNativeContext(native_context));

        GotoIfForceSlowPath(if_slow);
        GotoIfNot(IsJSPromiseMap(receiver_map), if_slow);
        Node* const promise_prototype = LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
        GotoIfNot(WordEqual(LoadMapPrototype(receiver_map), promise_prototype),
            if_slow);
        Branch(IsPromiseThenProtectorCellInvalid(), if_slow, if_fast);
    }

    void PromiseBuiltinsAssembler::BranchIfAccessCheckFailed(
        Node* context, Node* native_context, Node* promise_constructor,
        Node* executor, Label* if_noaccess)
    {
        VARIABLE(var_executor, MachineRepresentation::kTagged);
        var_executor.Bind(executor);
        Label has_access(this), call_runtime(this, Label::kDeferred);

        // If executor is a bound function, load the bound function until we've
        // reached an actual function.
        Label found_function(this), loop_over_bound_function(this, &var_executor);
        Goto(&loop_over_bound_function);
        BIND(&loop_over_bound_function);
        {
            Node* executor_type = LoadInstanceType(var_executor.value());
            GotoIf(InstanceTypeEqual(executor_type, JS_FUNCTION_TYPE), &found_function);
            GotoIfNot(InstanceTypeEqual(executor_type, JS_BOUND_FUNCTION_TYPE),
                &call_runtime);
            var_executor.Bind(LoadObjectField(
                var_executor.value(), JSBoundFunction::kBoundTargetFunctionOffset));
            Goto(&loop_over_bound_function);
        }

        // Load the context from the function and compare it to the Promise
        // constructor's context. If they match, everything is fine, otherwise, bail
        // out to the runtime.
        BIND(&found_function);
        {
            Node* function_context = LoadObjectField(var_executor.value(), JSFunction::kContextOffset);
            Node* native_function_context = LoadNativeContext(function_context);
            Branch(WordEqual(native_context, native_function_context), &has_access,
                &call_runtime);
        }

        BIND(&call_runtime);
        {
            Branch(WordEqual(CallRuntime(Runtime::kAllowDynamicFunction, context,
                                 promise_constructor),
                       TrueConstant()),
                &has_access, if_noaccess);
        }

        BIND(&has_access);
    }

    void PromiseBuiltinsAssembler::SetForwardingHandlerIfTrue(
        Node* context, Node* condition, const NodeGenerator& object)
    {
        Label done(this);
        GotoIfNot(condition, &done);
        SetPropertyStrict(
            CAST(context), CAST(object()),
            HeapConstant(factory()->promise_forwarding_handler_symbol()),
            TrueConstant());
        Goto(&done);
        BIND(&done);
    }

    void PromiseBuiltinsAssembler::SetPromiseHandledByIfTrue(
        Node* context, Node* condition, Node* promise,
        const NodeGenerator& handled_by)
    {
        Label done(this);
        GotoIfNot(condition, &done);
        GotoIf(TaggedIsSmi(promise), &done);
        GotoIfNot(HasInstanceType(promise, JS_PROMISE_TYPE), &done);
        SetPropertyStrict(CAST(context), CAST(promise),
            HeapConstant(factory()->promise_handled_by_symbol()),
            CAST(handled_by()));
        Goto(&done);
        BIND(&done);
    }

    // ES #sec-promise-reject-functions
    TF_BUILTIN(PromiseCapabilityDefaultReject, PromiseBuiltinsAssembler)
    {
        Node* const reason = Parameter(Descriptor::kReason);
        Node* const context = Parameter(Descriptor::kContext);

        // 2. Let promise be F.[[Promise]].
        Node* const promise = LoadContextElement(context, PromiseBuiltins::kPromiseSlot);

        // 3. Let alreadyResolved be F.[[AlreadyResolved]].
        Label if_already_resolved(this, Label::kDeferred);
        Node* const already_resolved = LoadContextElement(context, PromiseBuiltins::kAlreadyResolvedSlot);

        // 4. If alreadyResolved.[[Value]] is true, return undefined.
        GotoIf(IsTrue(already_resolved), &if_already_resolved);

        // 5. Set alreadyResolved.[[Value]] to true.
        StoreContextElementNoWriteBarrier(
            context, PromiseBuiltins::kAlreadyResolvedSlot, TrueConstant());

        // 6. Return RejectPromise(promise, reason).
        Node* const debug_event = LoadContextElement(context, PromiseBuiltins::kDebugEventSlot);
        Return(CallBuiltin(Builtins::kRejectPromise, context, promise, reason,
            debug_event));

        BIND(&if_already_resolved);
        {
            Return(CallRuntime(Runtime::kPromiseRejectAfterResolved, context, promise,
                reason));
        }
    }

    // ES #sec-promise-resolve-functions
    TF_BUILTIN(PromiseCapabilityDefaultResolve, PromiseBuiltinsAssembler)
    {
        Node* const resolution = Parameter(Descriptor::kResolution);
        Node* const context = Parameter(Descriptor::kContext);

        // 2. Let promise be F.[[Promise]].
        Node* const promise = LoadContextElement(context, PromiseBuiltins::kPromiseSlot);

        // 3. Let alreadyResolved be F.[[AlreadyResolved]].
        Label if_already_resolved(this, Label::kDeferred);
        Node* const already_resolved = LoadContextElement(context, PromiseBuiltins::kAlreadyResolvedSlot);

        // 4. If alreadyResolved.[[Value]] is true, return undefined.
        GotoIf(IsTrue(already_resolved), &if_already_resolved);

        // 5. Set alreadyResolved.[[Value]] to true.
        StoreContextElementNoWriteBarrier(
            context, PromiseBuiltins::kAlreadyResolvedSlot, TrueConstant());

        // The rest of the logic (and the catch prediction) is
        // encapsulated in the dedicated ResolvePromise builtin.
        Return(CallBuiltin(Builtins::kResolvePromise, context, promise, resolution));

        BIND(&if_already_resolved);
        {
            Return(CallRuntime(Runtime::kPromiseResolveAfterResolved, context, promise,
                resolution));
        }
    }

    TF_BUILTIN(PromiseConstructorLazyDeoptContinuation, PromiseBuiltinsAssembler)
    {
        Node* promise = Parameter(Descriptor::kPromise);
        Node* reject = Parameter(Descriptor::kReject);
        Node* exception = Parameter(Descriptor::kException);
        Node* const context = Parameter(Descriptor::kContext);

        Label finally(this);

        GotoIf(IsTheHole(exception), &finally);
        CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
            context, reject, UndefinedConstant(), exception);
        Goto(&finally);

        BIND(&finally);
        Return(promise);
    }

    // ES6 #sec-promise-executor
    TF_BUILTIN(PromiseConstructor, PromiseBuiltinsAssembler)
    {
        Node* const executor = Parameter(Descriptor::kExecutor);
        Node* const new_target = Parameter(Descriptor::kJSNewTarget);
        Node* const context = Parameter(Descriptor::kContext);
        Isolate* isolate = this->isolate();

        Label if_targetisundefined(this, Label::kDeferred);

        GotoIf(IsUndefined(new_target), &if_targetisundefined);

        Label if_notcallable(this, Label::kDeferred);

        GotoIf(TaggedIsSmi(executor), &if_notcallable);

        Node* const executor_map = LoadMap(executor);
        GotoIfNot(IsCallableMap(executor_map), &if_notcallable);

        Node* const native_context = LoadNativeContext(context);
        Node* const promise_fun = LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
        Node* const is_debug_active = IsDebugActive();
        Label if_targetisnotmodified(this),
            if_targetismodified(this, Label::kDeferred), run_executor(this),
            debug_push(this), if_noaccess(this, Label::kDeferred);

        BranchIfAccessCheckFailed(context, native_context, promise_fun, executor,
            &if_noaccess);

        Branch(WordEqual(promise_fun, new_target), &if_targetisnotmodified,
            &if_targetismodified);

        VARIABLE(var_result, MachineRepresentation::kTagged);
        VARIABLE(var_reject_call, MachineRepresentation::kTagged);
        VARIABLE(var_reason, MachineRepresentation::kTagged);

        BIND(&if_targetisnotmodified);
        {
            Node* const instance = AllocateAndInitJSPromise(context);
            var_result.Bind(instance);
            Goto(&debug_push);
        }

        BIND(&if_targetismodified);
        {
            ConstructorBuiltinsAssembler constructor_assembler(this->state());
            Node* const instance = constructor_assembler.EmitFastNewObject(
                context, promise_fun, new_target);
            PromiseInit(instance);
            var_result.Bind(instance);

            GotoIfNot(IsPromiseHookEnabledOrHasAsyncEventDelegate(), &debug_push);
            CallRuntime(Runtime::kPromiseHookInit, context, instance,
                UndefinedConstant());
            Goto(&debug_push);
        }

        BIND(&debug_push);
        {
            GotoIfNot(is_debug_active, &run_executor);
            CallRuntime(Runtime::kDebugPushPromise, context, var_result.value());
            Goto(&run_executor);
        }

        BIND(&run_executor);
        {
            Label out(this), if_rejectpromise(this), debug_pop(this, Label::kDeferred);

            Node *resolve, *reject;
            std::tie(resolve, reject) = CreatePromiseResolvingFunctions(
                var_result.value(), TrueConstant(), native_context);

            Node* const maybe_exception = CallJS(
                CodeFactory::Call(isolate, ConvertReceiverMode::kNullOrUndefined),
                context, executor, UndefinedConstant(), resolve, reject);

            GotoIfException(maybe_exception, &if_rejectpromise, &var_reason);
            Branch(is_debug_active, &debug_pop, &out);

            BIND(&if_rejectpromise);
            {
                CallJS(CodeFactory::Call(isolate, ConvertReceiverMode::kNullOrUndefined),
                    context, reject, UndefinedConstant(), var_reason.value());
                Branch(is_debug_active, &debug_pop, &out);
            }

            BIND(&debug_pop);
            {
                CallRuntime(Runtime::kDebugPopPromise, context);
                Goto(&out);
            }
            BIND(&out);
            Return(var_result.value());
        }

        // 1. If NewTarget is undefined, throw a TypeError exception.
        BIND(&if_targetisundefined);
        ThrowTypeError(context, MessageTemplate::kNotAPromise, new_target);

        // 2. If IsCallable(executor) is false, throw a TypeError exception.
        BIND(&if_notcallable);
        ThrowTypeError(context, MessageTemplate::kResolverNotAFunction, executor);

        // Silently fail if the stack looks fishy.
        BIND(&if_noaccess);
        {
            Node* const counter_id = SmiConstant(v8::Isolate::kPromiseConstructorReturnedUndefined);
            CallRuntime(Runtime::kIncrementUseCounter, context, counter_id);
            Return(UndefinedConstant());
        }
    }

    // V8 Extras: v8.createPromise(parent)
    TF_BUILTIN(PromiseInternalConstructor, PromiseBuiltinsAssembler)
    {
        Node* const parent = Parameter(Descriptor::kParent);
        Node* const context = Parameter(Descriptor::kContext);
        Return(AllocateAndInitJSPromise(context, parent));
    }

    // V8 Extras: v8.rejectPromise(promise, reason)
    TF_BUILTIN(PromiseInternalReject, PromiseBuiltinsAssembler)
    {
        Node* const promise = Parameter(Descriptor::kPromise);
        Node* const reason = Parameter(Descriptor::kReason);
        Node* const context = Parameter(Descriptor::kContext);

        // Main V8 Extras invariant that {promise} is still "pending" at
        // this point, aka that {promise} is not resolved multiple times.
        Label if_promise_is_settled(this, Label::kDeferred);
        GotoIfNot(IsPromiseStatus(PromiseStatus(promise), v8::Promise::kPending),
            &if_promise_is_settled);

        // We pass true to trigger the debugger's on exception handler.
        Return(CallBuiltin(Builtins::kRejectPromise, context, promise, reason,
            TrueConstant()));

        BIND(&if_promise_is_settled);
        Abort(AbortReason::kPromiseAlreadySettled);
    }

    // V8 Extras: v8.resolvePromise(promise, resolution)
    TF_BUILTIN(PromiseInternalResolve, PromiseBuiltinsAssembler)
    {
        Node* const promise = Parameter(Descriptor::kPromise);
        Node* const resolution = Parameter(Descriptor::kResolution);
        Node* const context = Parameter(Descriptor::kContext);

        // Main V8 Extras invariant that {promise} is still "pending" at
        // this point, aka that {promise} is not resolved multiple times.
        Label if_promise_is_settled(this, Label::kDeferred);
        GotoIfNot(IsPromiseStatus(PromiseStatus(promise), v8::Promise::kPending),
            &if_promise_is_settled);

        Return(CallBuiltin(Builtins::kResolvePromise, context, promise, resolution));

        BIND(&if_promise_is_settled);
        Abort(AbortReason::kPromiseAlreadySettled);
    }

    // ES#sec-promise.prototype.then
    // Promise.prototype.then ( onFulfilled, onRejected )
    TF_BUILTIN(PromisePrototypeThen, PromiseBuiltinsAssembler)
    {
        // 1. Let promise be the this value.
        Node* const promise = Parameter(Descriptor::kReceiver);
        Node* const on_fulfilled = Parameter(Descriptor::kOnFulfilled);
        Node* const on_rejected = Parameter(Descriptor::kOnRejected);
        Node* const context = Parameter(Descriptor::kContext);

        // 2. If IsPromise(promise) is false, throw a TypeError exception.
        ThrowIfNotInstanceType(context, promise, JS_PROMISE_TYPE,
            "Promise.prototype.then");

        // 3. Let C be ? SpeciesConstructor(promise, %Promise%).
        Label fast_promise_capability(this), slow_constructor(this, Label::kDeferred),
            slow_promise_capability(this, Label::kDeferred);
        Node* const native_context = LoadNativeContext(context);
        Node* const promise_fun = LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
        Node* const promise_map = LoadMap(promise);
        BranchIfPromiseSpeciesLookupChainIntact(
            native_context, promise_map, &fast_promise_capability, &slow_constructor);

        BIND(&slow_constructor);
        Node* const constructor = SpeciesConstructor(native_context, promise, promise_fun);
        Branch(WordEqual(constructor, promise_fun), &fast_promise_capability,
            &slow_promise_capability);

        // 4. Let resultCapability be ? NewPromiseCapability(C).
        Label perform_promise_then(this);
        VARIABLE(var_result_promise, MachineRepresentation::kTagged);
        VARIABLE(var_result_promise_or_capability, MachineRepresentation::kTagged);

        BIND(&fast_promise_capability);
        {
            Node* const result_promise = AllocateAndInitJSPromise(context, promise);
            var_result_promise_or_capability.Bind(result_promise);
            var_result_promise.Bind(result_promise);
            Goto(&perform_promise_then);
        }

        BIND(&slow_promise_capability);
        {
            Node* const debug_event = TrueConstant();
            Node* const capability = CallBuiltin(Builtins::kNewPromiseCapability,
                context, constructor, debug_event);
            var_result_promise.Bind(
                LoadObjectField(capability, PromiseCapability::kPromiseOffset));
            var_result_promise_or_capability.Bind(capability);
            Goto(&perform_promise_then);
        }

        // 5. Return PerformPromiseThen(promise, onFulfilled, onRejected,
        //    resultCapability).
        BIND(&perform_promise_then);
        {
            // We do some work of the PerformPromiseThen operation here, in that
            // we check the handlers and turn non-callable handlers into undefined.
            // This is because this is the one and only callsite of PerformPromiseThen
            // that has to do this.

            // 3. If IsCallable(onFulfilled) is false, then
            //    a. Set onFulfilled to undefined.
            VARIABLE(var_on_fulfilled, MachineRepresentation::kTagged, on_fulfilled);
            Label if_fulfilled_done(this), if_fulfilled_notcallable(this);
            GotoIf(TaggedIsSmi(on_fulfilled), &if_fulfilled_notcallable);
            Branch(IsCallable(on_fulfilled), &if_fulfilled_done,
                &if_fulfilled_notcallable);
            BIND(&if_fulfilled_notcallable);
            var_on_fulfilled.Bind(UndefinedConstant());
            Goto(&if_fulfilled_done);
            BIND(&if_fulfilled_done);

            // 4. If IsCallable(onRejected) is false, then
            //    a. Set onRejected to undefined.
            VARIABLE(var_on_rejected, MachineRepresentation::kTagged, on_rejected);
            Label if_rejected_done(this), if_rejected_notcallable(this);
            GotoIf(TaggedIsSmi(on_rejected), &if_rejected_notcallable);
            Branch(IsCallable(on_rejected), &if_rejected_done,
                &if_rejected_notcallable);
            BIND(&if_rejected_notcallable);
            var_on_rejected.Bind(UndefinedConstant());
            Goto(&if_rejected_done);
            BIND(&if_rejected_done);

            PerformPromiseThen(context, promise, var_on_fulfilled.value(),
                var_on_rejected.value(),
                var_result_promise_or_capability.value());
            Return(var_result_promise.value());
        }
    }

    // ES#sec-promise.prototype.catch
    // Promise.prototype.catch ( onRejected )
    TF_BUILTIN(PromisePrototypeCatch, PromiseBuiltinsAssembler)
    {
        // 1. Let promise be the this value.
        Node* const receiver = Parameter(Descriptor::kReceiver);
        Node* const on_fulfilled = UndefinedConstant();
        Node* const on_rejected = Parameter(Descriptor::kOnRejected);
        Node* const context = Parameter(Descriptor::kContext);

        // 2. Return ? Invoke(promise, "then", « undefined, onRejected »).
        Node* const native_context = LoadNativeContext(context);
        Return(InvokeThen(native_context, receiver, on_fulfilled, on_rejected));
    }

    // ES #sec-promiseresolvethenablejob
    TF_BUILTIN(PromiseResolveThenableJob, PromiseBuiltinsAssembler)
    {
        Node* const native_context = Parameter(Descriptor::kContext);
        Node* const promise_to_resolve = Parameter(Descriptor::kPromiseToResolve);
        Node* const thenable = Parameter(Descriptor::kThenable);
        Node* const then = Parameter(Descriptor::kThen);

        CSA_ASSERT(this, TaggedIsNotSmi(thenable));
        CSA_ASSERT(this, IsJSReceiver(thenable));
        CSA_ASSERT(this, IsJSPromise(promise_to_resolve));
        CSA_ASSERT(this, IsNativeContext(native_context));

        // We can use a simple optimization here if we know that {then} is the initial
        // Promise.prototype.then method, and {thenable} is a JSPromise whose
        // @@species lookup chain is intact: We can connect {thenable} and
        // {promise_to_resolve} directly in that case and avoid the allocation of a
        // temporary JSPromise and the closures plus context.
        //
        // We take the generic (slow-)path if a PromiseHook is enabled or the debugger
        // is active, to make sure we expose spec compliant behavior.
        Label if_fast(this), if_slow(this, Label::kDeferred);
        Node* const promise_then = LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
        GotoIfNot(WordEqual(then, promise_then), &if_slow);
        Node* const thenable_map = LoadMap(thenable);
        GotoIfNot(IsJSPromiseMap(thenable_map), &if_slow);
        GotoIf(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
            &if_slow);
        BranchIfPromiseSpeciesLookupChainIntact(native_context, thenable_map,
            &if_fast, &if_slow);

        BIND(&if_fast);
        {
            // We know that the {thenable} is a JSPromise, which doesn't require
            // any special treatment and that {then} corresponds to the initial
            // Promise.prototype.then method. So instead of allocating a temporary
            // JSPromise to connect the {thenable} with the {promise_to_resolve},
            // we can directly schedule the {promise_to_resolve} with default
            // handlers onto the {thenable} promise. This does not only save the
            // JSPromise allocation, but also avoids the allocation of the two
            // resolving closures and the shared context.
            //
            // What happens normally in this case is
            //
            //   resolve, reject = CreateResolvingFunctions(promise_to_resolve)
            //   result_capability = NewPromiseCapability(%Promise%)
            //   PerformPromiseThen(thenable, resolve, reject, result_capability)
            //
            // which means that PerformPromiseThen will either schedule a new
            // PromiseReaction with resolve and reject or a PromiseReactionJob
            // with resolve or reject based on the state of {thenable}. And
            // resolve or reject will just invoke the default [[Resolve]] or
            // [[Reject]] functions on the {promise_to_resolve}.
            //
            // This is the same as just doing
            //
            //   PerformPromiseThen(thenable, undefined, undefined, promise_to_resolve)
            //
            // which performs exactly the same (observable) steps.
            TailCallBuiltin(Builtins::kPerformPromiseThen, native_context, thenable,
                UndefinedConstant(), UndefinedConstant(),
                promise_to_resolve);
        }

        BIND(&if_slow);
        {
            Node* resolve = nullptr;
            Node* reject = nullptr;
            std::tie(resolve, reject) = CreatePromiseResolvingFunctions(
                promise_to_resolve, FalseConstant(), native_context);

            Label if_exception(this, Label::kDeferred);
            VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());
            Node* const result = CallJS(
                CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
                native_context, then, thenable, resolve, reject);
            GotoIfException(result, &if_exception, &var_exception);
            Return(result);

            BIND(&if_exception);
            {
                // We need to reject the {thenable}.
                Node* const result = CallJS(
                    CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
                    native_context, reject, UndefinedConstant(), var_exception.value());
                Return(result);
            }
        }
    }

    // ES #sec-promisereactionjob
    void PromiseBuiltinsAssembler::PromiseReactionJob(Node* context, Node* argument,
        Node* handler,
        Node* promise_or_capability,
        PromiseReaction::Type type)
    {
        CSA_ASSERT(this, TaggedIsNotSmi(handler));
        CSA_ASSERT(this, Word32Or(IsUndefined(handler), IsCallable(handler)));
        CSA_ASSERT(this, TaggedIsNotSmi(promise_or_capability));
        CSA_ASSERT(this,
            Word32Or(Word32Or(IsJSPromise(promise_or_capability),
                         IsPromiseCapability(promise_or_capability)),
                IsUndefined(promise_or_capability)));

        VARIABLE(var_handler_result, MachineRepresentation::kTagged, argument);
        Label if_handler_callable(this), if_fulfill(this), if_reject(this),
            if_internal(this);
        Branch(IsUndefined(handler),
            type == PromiseReaction::kFulfill ? &if_fulfill : &if_reject,
            &if_handler_callable);

        BIND(&if_handler_callable);
        {
            Node* const result = CallJS(
                CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
                context, handler, UndefinedConstant(), argument);
            GotoIfException(result, &if_reject, &var_handler_result);
            var_handler_result.Bind(result);
            Branch(IsUndefined(promise_or_capability), &if_internal, &if_fulfill);
        }

        BIND(&if_internal);
        {
            // There's no [[Capability]] for this promise reaction job, which
            // means that this is a specification-internal operation (aka await)
            // where the result does not matter (see the specification change in
            // https://github.com/tc39/ecma262/pull/1146 for details).
            Return(UndefinedConstant());
        }

        BIND(&if_fulfill);
        {
            Label if_promise(this), if_promise_capability(this, Label::kDeferred);
            Node* const value = var_handler_result.value();
            Branch(IsPromiseCapability(promise_or_capability), &if_promise_capability,
                &if_promise);

            BIND(&if_promise);
            {
                // For fast native promises we can skip the indirection
                // via the promiseCapability.[[Resolve]] function and
                // run the resolve logic directly from here.
                TailCallBuiltin(Builtins::kResolvePromise, context, promise_or_capability,
                    value);
            }

            BIND(&if_promise_capability);
            {
                // In the general case we need to call the (user provided)
                // promiseCapability.[[Resolve]] function.
                Node* const resolve = LoadObjectField(promise_or_capability,
                    PromiseCapability::kResolveOffset);
                Node* const result = CallJS(
                    CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
                    context, resolve, UndefinedConstant(), value);
                GotoIfException(result, &if_reject, &var_handler_result);
                Return(result);
            }
        }

        BIND(&if_reject);
        if (type == PromiseReaction::kReject) {
            Label if_promise(this), if_promise_capability(this, Label::kDeferred);
            Node* const reason = var_handler_result.value();
            Branch(IsPromiseCapability(promise_or_capability), &if_promise_capability,
                &if_promise);

            BIND(&if_promise);
            {
                // For fast native promises we can skip the indirection
                // via the promiseCapability.[[Reject]] function and
                // run the resolve logic directly from here.
                TailCallBuiltin(Builtins::kRejectPromise, context, promise_or_capability,
                    reason, FalseConstant());
            }

            BIND(&if_promise_capability);
            {
                // In the general case we need to call the (user provided)
                // promiseCapability.[[Reject]] function.
                Label if_exception(this, Label::kDeferred);
                VARIABLE(var_exception, MachineRepresentation::kTagged,
                    TheHoleConstant());
                Node* const reject = LoadObjectField(promise_or_capability,
                    PromiseCapability::kRejectOffset);
                Node* const result = CallJS(
                    CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
                    context, reject, UndefinedConstant(), reason);
                GotoIfException(result, &if_exception, &var_exception);
                Return(result);

                // Swallow the exception here.
                BIND(&if_exception);
                TailCallRuntime(Runtime::kReportMessage, context, var_exception.value());
            }
        } else {
            // We have to call out to the dedicated PromiseRejectReactionJob builtin
            // here, instead of just doing the work inline, as otherwise the catch
            // predictions in the debugger will be wrong, which just walks the stack
            // and checks for certain builtins.
            TailCallBuiltin(Builtins::kPromiseRejectReactionJob, context,
                var_handler_result.value(), UndefinedConstant(),
                promise_or_capability);
        }
    }

    // ES #sec-promisereactionjob
    TF_BUILTIN(PromiseFulfillReactionJob, PromiseBuiltinsAssembler)
    {
        Node* const context = Parameter(Descriptor::kContext);
        Node* const value = Parameter(Descriptor::kValue);
        Node* const handler = Parameter(Descriptor::kHandler);
        Node* const promise_or_capability = Parameter(Descriptor::kPromiseOrCapability);

        PromiseReactionJob(context, value, handler, promise_or_capability,
            PromiseReaction::kFulfill);
    }

    // ES #sec-promisereactionjob
    TF_BUILTIN(PromiseRejectReactionJob, PromiseBuiltinsAssembler)
    {
        Node* const context = Parameter(Descriptor::kContext);
        Node* const reason = Parameter(Descriptor::kReason);
        Node* const handler = Parameter(Descriptor::kHandler);
        Node* const promise_or_capability = Parameter(Descriptor::kPromiseOrCapability);

        PromiseReactionJob(context, reason, handler, promise_or_capability,
            PromiseReaction::kReject);
    }

    TF_BUILTIN(PromiseResolveTrampoline, PromiseBuiltinsAssembler)
    {
        //  1. Let C be the this value.
        Node* receiver = Parameter(Descriptor::kReceiver);
        Node* value = Parameter(Descriptor::kValue);
        Node* context = Parameter(Descriptor::kContext);

        // 2. If Type(C) is not Object, throw a TypeError exception.
        ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
            "PromiseResolve");

        // 3. Return ? PromiseResolve(C, x).
        Return(CallBuiltin(Builtins::kPromiseResolve, context, receiver, value));
    }

    TF_BUILTIN(PromiseResolve, PromiseBuiltinsAssembler)
    {
        Node* constructor = Parameter(Descriptor::kConstructor);
        Node* value = Parameter(Descriptor::kValue);
        Node* context = Parameter(Descriptor::kContext);

        CSA_ASSERT(this, IsJSReceiver(constructor));

        Node* const native_context = LoadNativeContext(context);
        Node* const promise_fun = LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);

        Label if_slow_constructor(this, Label::kDeferred), if_need_to_allocate(this);

        // Check if {value} is a JSPromise.
        GotoIf(TaggedIsSmi(value), &if_need_to_allocate);
        Node* const value_map = LoadMap(value);
        GotoIfNot(IsJSPromiseMap(value_map), &if_need_to_allocate);

        // We can skip the "constructor" lookup on {value} if it's [[Prototype]]
        // is the (initial) Promise.prototype and the @@species protector is
        // intact, as that guards the lookup path for "constructor" on
        // JSPromise instances which have the (initial) Promise.prototype.
        Node* const promise_prototype = LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
        GotoIfNot(WordEqual(LoadMapPrototype(value_map), promise_prototype),
            &if_slow_constructor);
        GotoIf(IsPromiseSpeciesProtectorCellInvalid(), &if_slow_constructor);

        // If the {constructor} is the Promise function, we just immediately
        // return the {value} here and don't bother wrapping it into a
        // native Promise.
        GotoIfNot(WordEqual(promise_fun, constructor), &if_slow_constructor);
        Return(value);

        // At this point, value or/and constructor are not native promises, but
        // they could be of the same subclass.
        BIND(&if_slow_constructor);
        {
            Node* const value_constructor = GetProperty(context, value, isolate()->factory()->constructor_string());
            GotoIfNot(WordEqual(value_constructor, constructor), &if_need_to_allocate);
            Return(value);
        }

        BIND(&if_need_to_allocate);
        {
            Label if_nativepromise(this), if_notnativepromise(this, Label::kDeferred);
            Branch(WordEqual(promise_fun, constructor), &if_nativepromise,
                &if_notnativepromise);

            // This adds a fast path for native promises that don't need to
            // create NewPromiseCapability.
            BIND(&if_nativepromise);
            {
                Node* const result = AllocateAndInitJSPromise(context);
                CallBuiltin(Builtins::kResolvePromise, context, result, value);
                Return(result);
            }

            BIND(&if_notnativepromise);
            {
                Node* const debug_event = TrueConstant();
                Node* const capability = CallBuiltin(Builtins::kNewPromiseCapability,
                    context, constructor, debug_event);

                Node* const resolve = LoadObjectField(capability, PromiseCapability::kResolveOffset);
                CallJS(
                    CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
                    context, resolve, UndefinedConstant(), value);

                Node* const result = LoadObjectField(capability, PromiseCapability::kPromiseOffset);
                Return(result);
            }
        }
    }

    // ES6 #sec-getcapabilitiesexecutor-functions
    TF_BUILTIN(PromiseGetCapabilitiesExecutor, PromiseBuiltinsAssembler)
    {
        Node* const resolve = Parameter(Descriptor::kResolve);
        Node* const reject = Parameter(Descriptor::kReject);
        Node* const context = Parameter(Descriptor::kContext);

        Node* const capability = LoadContextElement(context, PromiseBuiltins::kCapabilitySlot);

        Label if_alreadyinvoked(this, Label::kDeferred);
        GotoIfNot(IsUndefined(
                      LoadObjectField(capability, PromiseCapability::kResolveOffset)),
            &if_alreadyinvoked);
        GotoIfNot(IsUndefined(
                      LoadObjectField(capability, PromiseCapability::kRejectOffset)),
            &if_alreadyinvoked);

        StoreObjectField(capability, PromiseCapability::kResolveOffset, resolve);
        StoreObjectField(capability, PromiseCapability::kRejectOffset, reject);

        Return(UndefinedConstant());

        BIND(&if_alreadyinvoked);
        ThrowTypeError(context, MessageTemplate::kPromiseExecutorAlreadyInvoked);
    }

    TF_BUILTIN(PromiseReject, PromiseBuiltinsAssembler)
    {
        // 1. Let C be the this value.
        Node* const receiver = Parameter(Descriptor::kReceiver);
        Node* const reason = Parameter(Descriptor::kReason);
        Node* const context = Parameter(Descriptor::kContext);

        // 2. If Type(C) is not Object, throw a TypeError exception.
        ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
            "PromiseReject");

        Label if_nativepromise(this), if_custompromise(this, Label::kDeferred);
        Node* const native_context = LoadNativeContext(context);

        Node* const promise_fun = LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
        Branch(WordEqual(promise_fun, receiver), &if_nativepromise,
            &if_custompromise);

        BIND(&if_nativepromise);
        {
            Node* const promise = AllocateAndSetJSPromise(context, v8::Promise::kRejected, reason);
            CallRuntime(Runtime::kPromiseRejectEventFromStack, context, promise,
                reason);
            Return(promise);
        }

        BIND(&if_custompromise);
        {
            // 3. Let promiseCapability be ? NewPromiseCapability(C).
            Node* const debug_event = TrueConstant();
            Node* const capability = CallBuiltin(Builtins::kNewPromiseCapability,
                context, receiver, debug_event);

            // 4. Perform ? Call(promiseCapability.[[Reject]], undefined, « r »).
            Node* const reject = LoadObjectField(capability, PromiseCapability::kRejectOffset);
            CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
                context, reject, UndefinedConstant(), reason);

            // 5. Return promiseCapability.[[Promise]].
            Node* const promise = LoadObjectField(capability, PromiseCapability::kPromiseOffset);
            Return(promise);
        }
    }

    std::pair<Node*, Node*> PromiseBuiltinsAssembler::CreatePromiseFinallyFunctions(
        Node* on_finally, Node* constructor, Node* native_context)
    {
        Node* const promise_context = CreatePromiseContext(
            native_context, PromiseBuiltins::kPromiseFinallyContextLength);
        StoreContextElementNoWriteBarrier(
            promise_context, PromiseBuiltins::kOnFinallySlot, on_finally);
        StoreContextElementNoWriteBarrier(
            promise_context, PromiseBuiltins::kConstructorSlot, constructor);
        Node* const map = LoadContextElement(
            native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
        Node* const then_finally_info = LoadContextElement(
            native_context, Context::PROMISE_THEN_FINALLY_SHARED_FUN);
        Node* const then_finally = AllocateFunctionWithMapAndContext(
            map, then_finally_info, promise_context);
        Node* const catch_finally_info = LoadContextElement(
            native_context, Context::PROMISE_CATCH_FINALLY_SHARED_FUN);
        Node* const catch_finally = AllocateFunctionWithMapAndContext(
            map, catch_finally_info, promise_context);
        return std::make_pair(then_finally, catch_finally);
    }

    TF_BUILTIN(PromiseValueThunkFinally, PromiseBuiltinsAssembler)
    {
        Node* const context = Parameter(Descriptor::kContext);

        Node* const value = LoadContextElement(context, PromiseBuiltins::kValueSlot);
        Return(value);
    }

    Node* PromiseBuiltinsAssembler::CreateValueThunkFunction(Node* value,
        Node* native_context)
    {
        Node* const value_thunk_context = CreatePromiseContext(
            native_context, PromiseBuiltins::kPromiseValueThunkOrReasonContextLength);
        StoreContextElementNoWriteBarrier(value_thunk_context,
            PromiseBuiltins::kValueSlot, value);
        Node* const map = LoadContextElement(
            native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
        Node* const value_thunk_info = LoadContextElement(
            native_context, Context::PROMISE_VALUE_THUNK_FINALLY_SHARED_FUN);
        Node* const value_thunk = AllocateFunctionWithMapAndContext(
            map, value_thunk_info, value_thunk_context);
        return value_thunk;
    }

    TF_BUILTIN(PromiseThenFinally, PromiseBuiltinsAssembler)
    {
        CSA_ASSERT_JS_ARGC_EQ(this, 1);

        Node* const value = Parameter(Descriptor::kValue);
        Node* const context = Parameter(Descriptor::kContext);

        // 1. Let onFinally be F.[[OnFinally]].
        Node* const on_finally = LoadContextElement(context, PromiseBuiltins::kOnFinallySlot);

        // 2.  Assert: IsCallable(onFinally) is true.
        CSA_ASSERT(this, IsCallable(on_finally));

        // 3. Let result be ?  Call(onFinally).
        Node* const result = CallJS(
            CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
            context, on_finally, UndefinedConstant());

        // 4. Let C be F.[[Constructor]].
        Node* const constructor = LoadContextElement(context, PromiseBuiltins::kConstructorSlot);

        // 5. Assert: IsConstructor(C) is true.
        CSA_ASSERT(this, IsConstructor(constructor));

        // 6. Let promise be ? PromiseResolve(C, result).
        Node* const promise = CallBuiltin(Builtins::kPromiseResolve, context, constructor, result);

        // 7. Let valueThunk be equivalent to a function that returns value.
        Node* const native_context = LoadNativeContext(context);
        Node* const value_thunk = CreateValueThunkFunction(value, native_context);

        // 8. Return ? Invoke(promise, "then", « valueThunk »).
        Return(InvokeThen(native_context, promise, value_thunk));
    }

    TF_BUILTIN(PromiseThrowerFinally, PromiseBuiltinsAssembler)
    {
        Node* const context = Parameter(Descriptor::kContext);

        Node* const reason = LoadContextElement(context, PromiseBuiltins::kValueSlot);
        CallRuntime(Runtime::kThrow, context, reason);
        Unreachable();
    }

    Node* PromiseBuiltinsAssembler::CreateThrowerFunction(Node* reason,
        Node* native_context)
    {
        Node* const thrower_context = CreatePromiseContext(
            native_context, PromiseBuiltins::kPromiseValueThunkOrReasonContextLength);
        StoreContextElementNoWriteBarrier(thrower_context,
            PromiseBuiltins::kValueSlot, reason);
        Node* const map = LoadContextElement(
            native_context, Context::STRICT_FUNCTION_WITHOUT_PROTOTYPE_MAP_INDEX);
        Node* const thrower_info = LoadContextElement(
            native_context, Context::PROMISE_THROWER_FINALLY_SHARED_FUN);
        Node* const thrower = AllocateFunctionWithMapAndContext(map, thrower_info, thrower_context);
        return thrower;
    }

    TF_BUILTIN(PromiseCatchFinally, PromiseBuiltinsAssembler)
    {
        CSA_ASSERT_JS_ARGC_EQ(this, 1);

        Node* const reason = Parameter(Descriptor::kReason);
        Node* const context = Parameter(Descriptor::kContext);

        // 1. Let onFinally be F.[[OnFinally]].
        Node* const on_finally = LoadContextElement(context, PromiseBuiltins::kOnFinallySlot);

        // 2. Assert: IsCallable(onFinally) is true.
        CSA_ASSERT(this, IsCallable(on_finally));

        // 3. Let result be ? Call(onFinally).
        Node* result = CallJS(
            CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
            context, on_finally, UndefinedConstant());

        // 4. Let C be F.[[Constructor]].
        Node* const constructor = LoadContextElement(context, PromiseBuiltins::kConstructorSlot);

        // 5. Assert: IsConstructor(C) is true.
        CSA_ASSERT(this, IsConstructor(constructor));

        // 6. Let promise be ? PromiseResolve(C, result).
        Node* const promise = CallBuiltin(Builtins::kPromiseResolve, context, constructor, result);

        // 7. Let thrower be equivalent to a function that throws reason.
        Node* const native_context = LoadNativeContext(context);
        Node* const thrower = CreateThrowerFunction(reason, native_context);

        // 8. Return ? Invoke(promise, "then", « thrower »).
        Return(InvokeThen(native_context, promise, thrower));
    }

    TF_BUILTIN(PromisePrototypeFinally, PromiseBuiltinsAssembler)
    {
        CSA_ASSERT_JS_ARGC_EQ(this, 1);

        // 1.  Let promise be the this value.
        Node* const receiver = Parameter(Descriptor::kReceiver);
        Node* const on_finally = Parameter(Descriptor::kOnFinally);
        Node* const context = Parameter(Descriptor::kContext);

        // 2. If Type(promise) is not Object, throw a TypeError exception.
        ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
            "Promise.prototype.finally");

        // 3. Let C be ? SpeciesConstructor(promise, %Promise%).
        Node* const native_context = LoadNativeContext(context);
        Node* const promise_fun = LoadContextElement(native_context, Context::PROMISE_FUNCTION_INDEX);
        VARIABLE(var_constructor, MachineRepresentation::kTagged, promise_fun);
        Label slow_constructor(this, Label::kDeferred), done_constructor(this);
        Node* const receiver_map = LoadMap(receiver);
        GotoIfNot(IsJSPromiseMap(receiver_map), &slow_constructor);
        BranchIfPromiseSpeciesLookupChainIntact(native_context, receiver_map,
            &done_constructor, &slow_constructor);
        BIND(&slow_constructor);
        {
            Node* const constructor = SpeciesConstructor(context, receiver, promise_fun);
            var_constructor.Bind(constructor);
            Goto(&done_constructor);
        }
        BIND(&done_constructor);
        Node* const constructor = var_constructor.value();

        // 4. Assert: IsConstructor(C) is true.
        CSA_ASSERT(this, IsConstructor(constructor));

        VARIABLE(var_then_finally, MachineRepresentation::kTagged);
        VARIABLE(var_catch_finally, MachineRepresentation::kTagged);

        Label if_notcallable(this, Label::kDeferred), perform_finally(this);

        GotoIf(TaggedIsSmi(on_finally), &if_notcallable);
        GotoIfNot(IsCallable(on_finally), &if_notcallable);

        // 6. Else,
        //   a. Let thenFinally be a new built-in function object as defined
        //   in ThenFinally Function.
        //   b. Let catchFinally be a new built-in function object as
        //   defined in CatchFinally Function.
        //   c. Set thenFinally and catchFinally's [[Constructor]] internal
        //   slots to C.
        //   d. Set thenFinally and catchFinally's [[OnFinally]] internal
        //   slots to onFinally.
        Node* then_finally = nullptr;
        Node* catch_finally = nullptr;
        std::tie(then_finally, catch_finally) = CreatePromiseFinallyFunctions(on_finally, constructor, native_context);
        var_then_finally.Bind(then_finally);
        var_catch_finally.Bind(catch_finally);
        Goto(&perform_finally);

        // 5. If IsCallable(onFinally) is not true,
        //    a. Let thenFinally be onFinally.
        //    b. Let catchFinally be onFinally.
        BIND(&if_notcallable);
        {
            var_then_finally.Bind(on_finally);
            var_catch_finally.Bind(on_finally);
            Goto(&perform_finally);
        }

        // 7. Return ? Invoke(promise, "then", « thenFinally, catchFinally »).
        BIND(&perform_finally);
        Return(InvokeThen(native_context, receiver, var_then_finally.value(),
            var_catch_finally.value()));
    }

    // ES #sec-fulfillpromise
    TF_BUILTIN(FulfillPromise, PromiseBuiltinsAssembler)
    {
        Node* const promise = Parameter(Descriptor::kPromise);
        Node* const value = Parameter(Descriptor::kValue);
        Node* const context = Parameter(Descriptor::kContext);

        CSA_ASSERT(this, TaggedIsNotSmi(promise));
        CSA_ASSERT(this, IsJSPromise(promise));

        // 2. Let reactions be promise.[[PromiseFulfillReactions]].
        Node* const reactions = LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);

        // 3. Set promise.[[PromiseResult]] to value.
        // 4. Set promise.[[PromiseFulfillReactions]] to undefined.
        // 5. Set promise.[[PromiseRejectReactions]] to undefined.
        StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, value);

        // 6. Set promise.[[PromiseState]] to "fulfilled".
        PromiseSetStatus(promise, Promise::kFulfilled);

        // 7. Return TriggerPromiseReactions(reactions, value).
        Return(TriggerPromiseReactions(context, reactions, value,
            PromiseReaction::kFulfill));
    }

    // ES #sec-rejectpromise
    TF_BUILTIN(RejectPromise, PromiseBuiltinsAssembler)
    {
        Node* const promise = Parameter(Descriptor::kPromise);
        Node* const reason = Parameter(Descriptor::kReason);
        Node* const debug_event = Parameter(Descriptor::kDebugEvent);
        Node* const context = Parameter(Descriptor::kContext);

        CSA_ASSERT(this, TaggedIsNotSmi(promise));
        CSA_ASSERT(this, IsJSPromise(promise));
        CSA_ASSERT(this, IsBoolean(debug_event));
        Label if_runtime(this, Label::kDeferred);

        // If promise hook is enabled or the debugger is active, let
        // the runtime handle this operation, which greatly reduces
        // the complexity here and also avoids a couple of back and
        // forth between JavaScript and C++ land.
        GotoIf(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
            &if_runtime);

        // 7. If promise.[[PromiseIsHandled]] is false, perform
        //    HostPromiseRejectionTracker(promise, "reject").
        // We don't try to handle rejecting {promise} without handler
        // here, but we let the C++ code take care of this completely.
        GotoIfNot(PromiseHasHandler(promise), &if_runtime);

        // 2. Let reactions be promise.[[PromiseRejectReactions]].
        Node* reactions = LoadObjectField(promise, JSPromise::kReactionsOrResultOffset);

        // 3. Set promise.[[PromiseResult]] to reason.
        // 4. Set promise.[[PromiseFulfillReactions]] to undefined.
        // 5. Set promise.[[PromiseRejectReactions]] to undefined.
        StoreObjectField(promise, JSPromise::kReactionsOrResultOffset, reason);

        // 6. Set promise.[[PromiseState]] to "rejected".
        PromiseSetStatus(promise, Promise::kRejected);

        // 7. Return TriggerPromiseReactions(reactions, reason).
        Return(TriggerPromiseReactions(context, reactions, reason,
            PromiseReaction::kReject));

        BIND(&if_runtime);
        TailCallRuntime(Runtime::kRejectPromise, context, promise, reason,
            debug_event);
    }

    // ES #sec-promise-resolve-functions
    TF_BUILTIN(ResolvePromise, PromiseBuiltinsAssembler)
    {
        Node* const promise = Parameter(Descriptor::kPromise);
        Node* const resolution = Parameter(Descriptor::kResolution);
        Node* const context = Parameter(Descriptor::kContext);

        CSA_ASSERT(this, TaggedIsNotSmi(promise));
        CSA_ASSERT(this, IsJSPromise(promise));

        Label do_enqueue(this), if_fulfill(this), if_reject(this, Label::kDeferred),
            if_runtime(this, Label::kDeferred);
        VARIABLE(var_reason, MachineRepresentation::kTagged);
        VARIABLE(var_then, MachineRepresentation::kTagged);

        // If promise hook is enabled or the debugger is active, let
        // the runtime handle this operation, which greatly reduces
        // the complexity here and also avoids a couple of back and
        // forth between JavaScript and C++ land.
        GotoIf(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
            &if_runtime);

        // 6. If SameValue(resolution, promise) is true, then
        // We can use pointer comparison here, since the {promise} is guaranteed
        // to be a JSPromise inside this function and thus is reference comparable.
        GotoIf(WordEqual(promise, resolution), &if_runtime);

        // 7. If Type(resolution) is not Object, then
        GotoIf(TaggedIsSmi(resolution), &if_fulfill);
        Node* const resolution_map = LoadMap(resolution);
        GotoIfNot(IsJSReceiverMap(resolution_map), &if_fulfill);

        // We can skip the "then" lookup on {resolution} if its [[Prototype]]
        // is the (initial) Promise.prototype and the Promise#then protector
        // is intact, as that guards the lookup path for the "then" property
        // on JSPromise instances which have the (initial) %PromisePrototype%.
        Label if_fast(this), if_receiver(this), if_slow(this, Label::kDeferred);
        Node* const native_context = LoadNativeContext(context);
        GotoIfForceSlowPath(&if_slow);
        GotoIf(IsPromiseThenProtectorCellInvalid(), &if_slow);
        GotoIfNot(IsJSPromiseMap(resolution_map), &if_receiver);
        Node* const promise_prototype = LoadContextElement(native_context, Context::PROMISE_PROTOTYPE_INDEX);
        Branch(WordEqual(LoadMapPrototype(resolution_map), promise_prototype),
            &if_fast, &if_slow);

        BIND(&if_fast);
        {
            // The {resolution} is a native Promise in this case.
            Node* const then = LoadContextElement(native_context, Context::PROMISE_THEN_INDEX);
            var_then.Bind(then);
            Goto(&do_enqueue);
        }

        BIND(&if_receiver);
        {
            // We can skip the lookup of "then" if the {resolution} is a (newly
            // created) IterResultObject, as the Promise#then() protector also
            // ensures that the intrinsic %ObjectPrototype% doesn't contain any
            // "then" property. This helps to avoid negative lookups on iterator
            // results from async generators.
            CSA_ASSERT(this, IsJSReceiverMap(resolution_map));
            CSA_ASSERT(this, Word32BinaryNot(IsPromiseThenProtectorCellInvalid()));
            Node* const iterator_result_map = LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
            Branch(WordEqual(resolution_map, iterator_result_map), &if_fulfill,
                &if_slow);
        }

        BIND(&if_slow);
        {
            // 8. Let then be Get(resolution, "then").
            Node* const then = GetProperty(context, resolution, isolate()->factory()->then_string());

            // 9. If then is an abrupt completion, then
            GotoIfException(then, &if_reject, &var_reason);

            // 11. If IsCallable(thenAction) is false, then
            GotoIf(TaggedIsSmi(then), &if_fulfill);
            Node* const then_map = LoadMap(then);
            GotoIfNot(IsCallableMap(then_map), &if_fulfill);
            var_then.Bind(then);
            Goto(&do_enqueue);
        }

        BIND(&do_enqueue);
        {
            // 12. Perform EnqueueJob("PromiseJobs", PromiseResolveThenableJob,
            //                        «promise, resolution, thenAction»).
            Node* const task = AllocatePromiseResolveThenableJobTask(
                promise, var_then.value(), resolution, native_context);
            TailCallBuiltin(Builtins::kEnqueueMicrotask, native_context, task);
        }

        BIND(&if_fulfill);
        {
            // 7.b Return FulfillPromise(promise, resolution).
            TailCallBuiltin(Builtins::kFulfillPromise, context, promise, resolution);
        }

        BIND(&if_runtime);
        Return(CallRuntime(Runtime::kResolvePromise, context, promise, resolution));

        BIND(&if_reject);
        {
            // 9.a Return RejectPromise(promise, then.[[Value]]).
            TailCallBuiltin(Builtins::kRejectPromise, context, promise,
                var_reason.value(), FalseConstant());
        }
    }

    Node* PromiseBuiltinsAssembler::PerformPromiseAll(
        Node* context, Node* constructor, Node* capability,
        const IteratorRecord& iterator,
        const PromiseAllResolvingElementFunction& create_resolve_element_function,
        const PromiseAllResolvingElementFunction& create_reject_element_function,
        Label* if_exception, Variable* var_exception)
    {
        IteratorBuiltinsAssembler iter_assembler(state());

        TNode<NativeContext> native_context = Cast(LoadNativeContext(context));

        // For catch prediction, don't treat the .then calls as handling it;
        // instead, recurse outwards.
        SetForwardingHandlerIfTrue(
            native_context, IsDebugActive(),
            LoadObjectField(capability, PromiseCapability::kRejectOffset));

        TNode<Context> resolve_element_context = Cast(CreatePromiseAllResolveElementContext(capability, native_context));

        TVARIABLE(Smi, var_index, SmiConstant(1));
        Label loop(this, &var_index), done_loop(this),
            too_many_elements(this, Label::kDeferred),
            close_iterator(this, Label::kDeferred);
        Goto(&loop);
        BIND(&loop);
        {
            // Let next be IteratorStep(iteratorRecord.[[Iterator]]).
            // If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
            // ReturnIfAbrupt(next).
            Node* const fast_iterator_result_map = LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX);
            Node* const next = iter_assembler.IteratorStep(
                native_context, iterator, &done_loop, fast_iterator_result_map,
                if_exception, var_exception);

            // Let nextValue be IteratorValue(next).
            // If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to
            //     true.
            // ReturnIfAbrupt(nextValue).
            Node* const next_value = iter_assembler.IteratorValue(
                native_context, next, fast_iterator_result_map, if_exception,
                var_exception);

            // Check if we reached the limit.
            TNode<Smi> const index = var_index.value();
            GotoIf(SmiEqual(index, SmiConstant(PropertyArray::HashField::kMax)),
                &too_many_elements);

            // Set index to index + 1.
            var_index = SmiAdd(index, SmiConstant(1));

            // Set remainingElementsCount.[[Value]] to
            //     remainingElementsCount.[[Value]] + 1.
            TNode<Smi> const remaining_elements_count = CAST(LoadContextElement(
                resolve_element_context,
                PromiseBuiltins::kPromiseAllResolveElementRemainingSlot));
            StoreContextElementNoWriteBarrier(
                resolve_element_context,
                PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
                SmiAdd(remaining_elements_count, SmiConstant(1)));

            // Let resolveElement be CreateBuiltinFunction(steps,
            //                                             « [[AlreadyCalled]],
            //                                               [[Index]],
            //                                               [[Values]],
            //                                               [[Capability]],
            //                                               [[RemainingElements]] »).
            // Set resolveElement.[[AlreadyCalled]] to a Record { [[Value]]: false }.
            // Set resolveElement.[[Index]] to index.
            // Set resolveElement.[[Values]] to values.
            // Set resolveElement.[[Capability]] to resultCapability.
            // Set resolveElement.[[RemainingElements]] to remainingElementsCount.
            Node* const resolve_element_fun = create_resolve_element_function(
                resolve_element_context, index, native_context, Cast(capability));
            Node* const reject_element_fun = create_reject_element_function(
                resolve_element_context, index, native_context, Cast(capability));

            // We can skip the "resolve" lookup on the {constructor} as well as the
            // "then" lookup on the result of the "resolve" call, and immediately
            // chain continuation onto the {next_value} if:
            //
            //   (a) The {constructor} is the intrinsic %Promise% function, and
            //       looking up "resolve" on {constructor} yields the initial
            //       Promise.resolve() builtin, and
            //   (b) the promise @@species protector cell is valid, meaning that
            //       no one messed with the Symbol.species property on any
            //       intrinsic promise or on the Promise.prototype, and
            //   (c) the {next_value} is a JSPromise whose [[Prototype]] field
            //       contains the intrinsic %PromisePrototype%, and
            //   (d) we're not running with async_hooks or DevTools enabled.
            //
            // In that case we also don't need to allocate a chained promise for
            // the PromiseReaction (aka we can pass undefined to PerformPromiseThen),
            // since this is only necessary for DevTools and PromiseHooks.
            Label if_fast(this), if_slow(this);
            GotoIfNotPromiseResolveLookupChainIntact(native_context, constructor,
                &if_slow);
            GotoIf(IsPromiseHookEnabledOrDebugIsActiveOrHasAsyncEventDelegate(),
                &if_slow);
            GotoIf(IsPromiseSpeciesProtectorCellInvalid(), &if_slow);
            GotoIf(TaggedIsSmi(next_value), &if_slow);
            Node* const next_value_map = LoadMap(next_value);
            BranchIfPromiseThenLookupChainIntact(native_context, next_value_map,
                &if_fast, &if_slow);

            BIND(&if_fast);
            {
                // Register the PromiseReaction immediately on the {next_value}, not
                // passing any chained promise since neither async_hooks nor DevTools
                // are enabled, so there's no use of the resulting promise.
                PerformPromiseThen(native_context, next_value, resolve_element_fun,
                    reject_element_fun, UndefinedConstant());
                Goto(&loop);
            }

            BIND(&if_slow);
            {
                // Let nextPromise be ? Invoke(constructor, "resolve", « nextValue »).
                Node* const next_promise = InvokeResolve(native_context, constructor, next_value,
                    &close_iterator, var_exception);

                // Perform ? Invoke(nextPromise, "then", « resolveElement,
                //                  resultCapability.[[Reject]] »).
                Node* const then = GetProperty(native_context, next_promise, factory()->then_string());
                GotoIfException(then, &close_iterator, var_exception);

                Node* const then_call = CallJS(CodeFactory::Call(isolate(),
                                                   ConvertReceiverMode::kNotNullOrUndefined),
                    native_context, then, next_promise, resolve_element_fun,
                    reject_element_fun);
                GotoIfException(then_call, &close_iterator, var_exception);

                // For catch prediction, mark that rejections here are semantically
                // handled by the combined Promise.
                SetPromiseHandledByIfTrue(
                    native_context, IsDebugActive(), then_call, [=]() {
                        // Load promiseCapability.[[Promise]]
                        return LoadObjectField(capability,
                            PromiseCapability::kPromiseOffset);
                    });

                Goto(&loop);
            }
        }

        BIND(&too_many_elements);
        {
            // If there are too many elements (currently more than 2**21-1), raise a
            // RangeError here (which is caught directly and turned into a rejection)
            // of the resulting promise. We could gracefully handle this case as well
            // and support more than this number of elements by going to a separate
            // function and pass the larger indices via a separate context, but it
            // doesn't seem likely that we need this, and it's unclear how the rest
            // of the system deals with 2**21 live Promises anyways.
            Node* const result = CallRuntime(Runtime::kThrowRangeError, native_context,
                SmiConstant(MessageTemplate::kTooManyElementsInPromiseAll));
            GotoIfException(result, &close_iterator, var_exception);
            Unreachable();
        }

        BIND(&close_iterator);
        {
            // Exception must be bound to a JS value.
            CSA_ASSERT(this, IsNotTheHole(var_exception->value()));
            iter_assembler.IteratorCloseOnException(native_context, iterator,
                if_exception, var_exception);
        }

        BIND(&done_loop);
        {
            Label resolve_promise(this, Label::kDeferred), return_promise(this);
            // Set iteratorRecord.[[Done]] to true.
            // Set remainingElementsCount.[[Value]] to
            //    remainingElementsCount.[[Value]] - 1.
            TNode<Smi> remaining_elements_count = CAST(LoadContextElement(
                resolve_element_context,
                PromiseBuiltins::kPromiseAllResolveElementRemainingSlot));
            remaining_elements_count = SmiSub(remaining_elements_count, SmiConstant(1));
            StoreContextElementNoWriteBarrier(
                resolve_element_context,
                PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
                remaining_elements_count);
            GotoIf(SmiEqual(remaining_elements_count, SmiConstant(0)),
                &resolve_promise);

            // Pre-allocate the backing store for the {values_array} to the desired
            // capacity here. We may already have elements here in case of some
            // fancy Thenable that calls the resolve callback immediately, so we need
            // to handle that correctly here.
            Node* const values_array = LoadContextElement(
                resolve_element_context,
                PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot);
            Node* const old_elements = LoadElements(values_array);
            TNode<Smi> const old_capacity = LoadFixedArrayBaseLength(old_elements);
            TNode<Smi> const new_capacity = var_index.value();
            GotoIf(SmiGreaterThanOrEqual(old_capacity, new_capacity), &return_promise);
            Node* const new_elements = AllocateFixedArray(PACKED_ELEMENTS, new_capacity, SMI_PARAMETERS,
                AllocationFlag::kAllowLargeObjectAllocation);
            CopyFixedArrayElements(PACKED_ELEMENTS, old_elements, PACKED_ELEMENTS,
                new_elements, SmiConstant(0), old_capacity,
                new_capacity, UPDATE_WRITE_BARRIER, SMI_PARAMETERS);
            StoreObjectField(values_array, JSArray::kElementsOffset, new_elements);
            Goto(&return_promise);

            // If remainingElementsCount.[[Value]] is 0, then
            //     Let valuesArray be CreateArrayFromList(values).
            //     Perform ? Call(resultCapability.[[Resolve]], undefined,
            //                    « valuesArray »).
            BIND(&resolve_promise);
            {
                Node* const resolve = LoadObjectField(capability, PromiseCapability::kResolveOffset);
                Node* const values_array = LoadContextElement(
                    resolve_element_context,
                    PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot);
                Node* const resolve_call = CallJS(
                    CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
                    native_context, resolve, UndefinedConstant(), values_array);
                GotoIfException(resolve_call, if_exception, var_exception);
                Goto(&return_promise);
            }

            // Return resultCapability.[[Promise]].
            BIND(&return_promise);
        }

        Node* const promise = LoadObjectField(capability, PromiseCapability::kPromiseOffset);
        return promise;
    }

    void PromiseBuiltinsAssembler::Generate_PromiseAll(
        TNode<Context> context, TNode<Object> receiver, TNode<Object> iterable,
        const PromiseAllResolvingElementFunction& create_resolve_element_function,
        const PromiseAllResolvingElementFunction& create_reject_element_function)
    {
        IteratorBuiltinsAssembler iter_assembler(state());

        // Let C be the this value.
        // If Type(C) is not Object, throw a TypeError exception.
        ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
            "Promise.all");

        // Let promiseCapability be ? NewPromiseCapability(C).
        // Don't fire debugEvent so that forwarding the rejection through all does not
        // trigger redundant ExceptionEvents
        Node* const debug_event = FalseConstant();
        Node* const capability = CallBuiltin(Builtins::kNewPromiseCapability, context,
            receiver, debug_event);

        VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());
        Label reject_promise(this, &var_exception, Label::kDeferred);

        // Let iterator be GetIterator(iterable).
        // IfAbruptRejectPromise(iterator, promiseCapability).
        IteratorRecord iterator = iter_assembler.GetIterator(
            context, iterable, &reject_promise, &var_exception);

        // Let result be PerformPromiseAll(iteratorRecord, C, promiseCapability).
        // If result is an abrupt completion, then
        //   If iteratorRecord.[[Done]] is false, let result be
        //       IteratorClose(iterator, result).
        //    IfAbruptRejectPromise(result, promiseCapability).
        Node* const result = PerformPromiseAll(
            context, receiver, capability, iterator, create_resolve_element_function,
            create_reject_element_function, &reject_promise, &var_exception);

        Return(result);

        BIND(&reject_promise);
        {
            // Exception must be bound to a JS value.
            CSA_SLOW_ASSERT(this, IsNotTheHole(var_exception.value()));
            Node* const reject = LoadObjectField(capability, PromiseCapability::kRejectOffset);
            CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
                context, reject, UndefinedConstant(), var_exception.value());

            Node* const promise = LoadObjectField(capability, PromiseCapability::kPromiseOffset);
            Return(promise);
        }
    }

    // ES#sec-promise.all
    // Promise.all ( iterable )
    TF_BUILTIN(PromiseAll, PromiseBuiltinsAssembler)
    {
        TNode<Object> receiver = Cast(Parameter(Descriptor::kReceiver));
        TNode<Context> context = Cast(Parameter(Descriptor::kContext));
        TNode<Object> iterable = Cast(Parameter(Descriptor::kIterable));
        Generate_PromiseAll(
            context, receiver, iterable,
            [this](TNode<Context> context, TNode<Smi> index,
                TNode<NativeContext> native_context,
                TNode<PromiseCapability> capability) {
                return CreatePromiseAllResolveElementFunction(
                    context, index, native_context,
                    Context::PROMISE_ALL_RESOLVE_ELEMENT_SHARED_FUN);
            },
            [this](TNode<Context> context, TNode<Smi> index,
                TNode<NativeContext> native_context,
                TNode<PromiseCapability> capability) {
                return LoadObjectField(capability, PromiseCapability::kRejectOffset);
            });
    }

    // ES#sec-promise.allsettled
    // Promise.allSettled ( iterable )
    TF_BUILTIN(PromiseAllSettled, PromiseBuiltinsAssembler)
    {
        TNode<Object> receiver = Cast(Parameter(Descriptor::kReceiver));
        TNode<Context> context = Cast(Parameter(Descriptor::kContext));
        TNode<Object> iterable = Cast(Parameter(Descriptor::kIterable));
        Generate_PromiseAll(
            context, receiver, iterable,
            [this](TNode<Context> context, TNode<Smi> index,
                TNode<NativeContext> native_context,
                TNode<PromiseCapability> capability) {
                return CreatePromiseAllResolveElementFunction(
                    context, index, native_context,
                    Context::PROMISE_ALL_SETTLED_RESOLVE_ELEMENT_SHARED_FUN);
            },
            [this](TNode<Context> context, TNode<Smi> index,
                TNode<NativeContext> native_context,
                TNode<PromiseCapability> capability) {
                return CreatePromiseAllResolveElementFunction(
                    context, index, native_context,
                    Context::PROMISE_ALL_SETTLED_REJECT_ELEMENT_SHARED_FUN);
            });
    }

    void PromiseBuiltinsAssembler::Generate_PromiseAllResolveElementClosure(
        TNode<Context> context, TNode<Object> value, TNode<JSFunction> function,
        const CreatePromiseAllResolveElementFunctionValue& callback)
    {
        Label already_called(this, Label::kDeferred), resolve_promise(this);

        // We use the {function}s context as the marker to remember whether this
        // resolve element closure was already called. It points to the resolve
        // element context (which is a FunctionContext) until it was called the
        // first time, in which case we make it point to the native context here
        // to mark this resolve element closure as done.
        GotoIf(IsNativeContext(context), &already_called);
        CSA_ASSERT(
            this,
            SmiEqual(LoadObjectField<Smi>(context, Context::kLengthOffset),
                SmiConstant(PromiseBuiltins::kPromiseAllResolveElementLength)));
        TNode<NativeContext> native_context = Cast(LoadNativeContext(context));
        StoreObjectField(function, JSFunction::kContextOffset, native_context);

        // Update the value depending on whether Promise.all or
        // Promise.allSettled is called.
        value = callback(context, native_context, value);

        // Determine the index from the {function}.
        Label unreachable(this, Label::kDeferred);
        STATIC_ASSERT(PropertyArray::kNoHashSentinel == 0);
        TNode<IntPtrT> identity_hash = LoadJSReceiverIdentityHash(function, &unreachable);
        CSA_ASSERT(this, IntPtrGreaterThan(identity_hash, IntPtrConstant(0)));
        TNode<IntPtrT> index = IntPtrSub(identity_hash, IntPtrConstant(1));

        // Check if we need to grow the [[ValuesArray]] to store {value} at {index}.
        TNode<JSArray> values_array = CAST(LoadContextElement(
            context, PromiseBuiltins::kPromiseAllResolveElementValuesArraySlot));
        TNode<FixedArray> elements = CAST(LoadElements(values_array));
        TNode<IntPtrT> values_length = LoadAndUntagObjectField(values_array, JSArray::kLengthOffset);
        Label if_inbounds(this), if_outofbounds(this), done(this);
        Branch(IntPtrLessThan(index, values_length), &if_inbounds, &if_outofbounds);

        BIND(&if_outofbounds);
        {
            // Check if we need to grow the backing store.
            TNode<IntPtrT> new_length = IntPtrAdd(index, IntPtrConstant(1));
            TNode<IntPtrT> elements_length = LoadAndUntagObjectField(elements, FixedArray::kLengthOffset);
            Label if_grow(this, Label::kDeferred), if_nogrow(this);
            Branch(IntPtrLessThan(index, elements_length), &if_nogrow, &if_grow);

            BIND(&if_grow);
            {
                // We need to grow the backing store to fit the {index} as well.
                TNode<IntPtrT> new_elements_length = IntPtrMin(CalculateNewElementsCapacity(new_length),
                    IntPtrConstant(PropertyArray::HashField::kMax + 1));
                CSA_ASSERT(this, IntPtrLessThan(index, new_elements_length));
                CSA_ASSERT(this, IntPtrLessThan(elements_length, new_elements_length));
                TNode<FixedArray> new_elements = CAST(AllocateFixedArray(PACKED_ELEMENTS, new_elements_length,
                    AllocationFlag::kAllowLargeObjectAllocation));
                CopyFixedArrayElements(PACKED_ELEMENTS, elements, PACKED_ELEMENTS,
                    new_elements, elements_length,
                    new_elements_length);
                StoreFixedArrayElement(new_elements, index, value);

                // Update backing store and "length" on {values_array}.
                StoreObjectField(values_array, JSArray::kElementsOffset, new_elements);
                StoreObjectFieldNoWriteBarrier(values_array, JSArray::kLengthOffset,
                    SmiTag(new_length));
                Goto(&done);
            }

            BIND(&if_nogrow);
            {
                // The {index} is within bounds of the {elements} backing store, so
                // just store the {value} and update the "length" of the {values_array}.
                StoreObjectFieldNoWriteBarrier(values_array, JSArray::kLengthOffset,
                    SmiTag(new_length));
                StoreFixedArrayElement(elements, index, value);
                Goto(&done);
            }
        }

        BIND(&if_inbounds);
        {
            // The {index} is in bounds of the {values_array},
            // just store the {value} and continue.
            StoreFixedArrayElement(elements, index, value);
            Goto(&done);
        }

        BIND(&done);
        TNode<Smi> remaining_elements_count = CAST(LoadContextElement(
            context, PromiseBuiltins::kPromiseAllResolveElementRemainingSlot));
        remaining_elements_count = SmiSub(remaining_elements_count, SmiConstant(1));
        StoreContextElement(context,
            PromiseBuiltins::kPromiseAllResolveElementRemainingSlot,
            remaining_elements_count);
        GotoIf(SmiEqual(remaining_elements_count, SmiConstant(0)), &resolve_promise);
        Return(UndefinedConstant());

        BIND(&resolve_promise);
        TNode<PromiseCapability> capability = CAST(LoadContextElement(
            context, PromiseBuiltins::kPromiseAllResolveElementCapabilitySlot));
        TNode<Object> resolve = LoadObjectField(capability, PromiseCapability::kResolveOffset);
        CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
            context, resolve, UndefinedConstant(), values_array);
        Return(UndefinedConstant());

        BIND(&already_called);
        Return(UndefinedConstant());

        BIND(&unreachable);
        Unreachable();
    }

    TF_BUILTIN(PromiseAllResolveElementClosure, PromiseBuiltinsAssembler)
    {
        TNode<Object> value = CAST(Parameter(Descriptor::kValue));
        TNode<Context> context = CAST(Parameter(Descriptor::kContext));
        TNode<JSFunction> function = CAST(Parameter(Descriptor::kJSTarget));

        Generate_PromiseAllResolveElementClosure(
            context, value, function,
            [](TNode<Object>, TNode<NativeContext>, TNode<Object> value) {
                return value;
            });
    }

    TF_BUILTIN(PromiseAllSettledResolveElementClosure, PromiseBuiltinsAssembler)
    {
        TNode<Object> value = CAST(Parameter(Descriptor::kValue));
        TNode<Context> context = CAST(Parameter(Descriptor::kContext));
        TNode<JSFunction> function = CAST(Parameter(Descriptor::kJSTarget));

        Generate_PromiseAllResolveElementClosure(
            context, value, function,
            [this](TNode<Context> context, TNode<NativeContext> native_context,
                TNode<Object> value) {
                // TODO(gsathya): Optimize the creation using a cached map to
                // prevent transitions here.
                // 9. Let obj be ! ObjectCreate(%ObjectPrototype%).
                TNode<HeapObject> object_function = Cast(
                    LoadContextElement(native_context, Context::OBJECT_FUNCTION_INDEX));
                TNode<Map> object_function_map = Cast(LoadObjectField(
                    object_function, JSFunction::kPrototypeOrInitialMapOffset));
                TNode<JSObject> obj = Cast(AllocateJSObjectFromMap(object_function_map));

                // 10. Perform ! CreateDataProperty(obj, "status", "fulfilled").
                CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
                    StringConstant("status"), StringConstant("fulfilled"));

                // 11. Perform ! CreateDataProperty(obj, "value", x).
                CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
                    StringConstant("value"), value);

                return obj;
            });
    }

    TF_BUILTIN(PromiseAllSettledRejectElementClosure, PromiseBuiltinsAssembler)
    {
        TNode<Object> value = CAST(Parameter(Descriptor::kValue));
        TNode<Context> context = CAST(Parameter(Descriptor::kContext));
        TNode<JSFunction> function = CAST(Parameter(Descriptor::kJSTarget));

        Generate_PromiseAllResolveElementClosure(
            context, value, function,
            [this](TNode<Context> context, TNode<NativeContext> native_context,
                TNode<Object> value) {
                // TODO(gsathya): Optimize the creation using a cached map to
                // prevent transitions here.
                // 9. Let obj be ! ObjectCreate(%ObjectPrototype%).
                TNode<HeapObject> object_function = Cast(
                    LoadContextElement(native_context, Context::OBJECT_FUNCTION_INDEX));
                TNode<Map> object_function_map = Cast(LoadObjectField(
                    object_function, JSFunction::kPrototypeOrInitialMapOffset));
                TNode<JSObject> obj = Cast(AllocateJSObjectFromMap(object_function_map));

                // 10. Perform ! CreateDataProperty(obj, "status", "rejected").
                CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
                    StringConstant("status"), StringConstant("rejected"));

                // 11. Perform ! CreateDataProperty(obj, "reason", x).
                CallBuiltin(Builtins::kFastCreateDataProperty, context, obj,
                    StringConstant("reason"), value);

                return obj;
            });
    }

    // ES#sec-promise.race
    // Promise.race ( iterable )
    TF_BUILTIN(PromiseRace, PromiseBuiltinsAssembler)
    {
        IteratorBuiltinsAssembler iter_assembler(state());
        VARIABLE(var_exception, MachineRepresentation::kTagged, TheHoleConstant());

        Node* const receiver = Parameter(Descriptor::kReceiver);
        Node* const context = Parameter(Descriptor::kContext);
        ThrowIfNotJSReceiver(context, receiver, MessageTemplate::kCalledOnNonObject,
            "Promise.race");

        // Let promiseCapability be ? NewPromiseCapability(C).
        // Don't fire debugEvent so that forwarding the rejection through all does not
        // trigger redundant ExceptionEvents
        Node* const debug_event = FalseConstant();
        Node* const capability = CallBuiltin(Builtins::kNewPromiseCapability, context,
            receiver, debug_event);

        Node* const resolve = LoadObjectField(capability, PromiseCapability::kResolveOffset);
        Node* const reject = LoadObjectField(capability, PromiseCapability::kRejectOffset);

        Label close_iterator(this, Label::kDeferred);
        Label reject_promise(this, Label::kDeferred);

        // For catch prediction, don't treat the .then calls as handling it;
        // instead, recurse outwards.
        SetForwardingHandlerIfTrue(context, IsDebugActive(), reject);

        // Let iterator be GetIterator(iterable).
        // IfAbruptRejectPromise(iterator, promiseCapability).
        Node* const iterable = Parameter(Descriptor::kIterable);
        IteratorRecord iterator = iter_assembler.GetIterator(
            context, iterable, &reject_promise, &var_exception);

        // Let result be PerformPromiseRace(iteratorRecord, C, promiseCapability).
        {
            Label loop(this), break_loop(this);
            Goto(&loop);
            BIND(&loop);
            {
                Node* const native_context = LoadNativeContext(context);
                Node* const fast_iterator_result_map = LoadContextElement(
                    native_context, Context::ITERATOR_RESULT_MAP_INDEX);

                // Let next be IteratorStep(iteratorRecord.[[Iterator]]).
                // If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
                // ReturnIfAbrupt(next).
                Node* const next = iter_assembler.IteratorStep(
                    context, iterator, &break_loop, fast_iterator_result_map,
                    &reject_promise, &var_exception);

                // Let nextValue be IteratorValue(next).
                // If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to
                //     true.
                // ReturnIfAbrupt(nextValue).
                Node* const next_value = iter_assembler.IteratorValue(context, next, fast_iterator_result_map,
                    &reject_promise, &var_exception);

                // Let nextPromise be ? Invoke(constructor, "resolve", « nextValue »).
                Node* const next_promise = InvokeResolve(native_context, receiver, next_value, &close_iterator,
                    &var_exception);

                // Perform ? Invoke(nextPromise, "then", « resolveElement,
                //                  resultCapability.[[Reject]] »).
                Node* const then = GetProperty(context, next_promise, factory()->then_string());
                GotoIfException(then, &close_iterator, &var_exception);

                Node* const then_call = CallJS(CodeFactory::Call(isolate(),
                                                   ConvertReceiverMode::kNotNullOrUndefined),
                    context, then, next_promise, resolve, reject);
                GotoIfException(then_call, &close_iterator, &var_exception);

                // For catch prediction, mark that rejections here are semantically
                // handled by the combined Promise.
                SetPromiseHandledByIfTrue(context, IsDebugActive(), then_call, [=]() {
                    // Load promiseCapability.[[Promise]]
                    return LoadObjectField(capability, PromiseCapability::kPromiseOffset);
                });
                Goto(&loop);
            }

            BIND(&break_loop);
            Return(LoadObjectField(capability, PromiseCapability::kPromiseOffset));
        }

        BIND(&close_iterator);
        {
            CSA_ASSERT(this, IsNotTheHole(var_exception.value()));
            iter_assembler.IteratorCloseOnException(context, iterator, &reject_promise,
                &var_exception);
        }

        BIND(&reject_promise);
        {
            Node* const reject = LoadObjectField(capability, PromiseCapability::kRejectOffset);
            CallJS(CodeFactory::Call(isolate(), ConvertReceiverMode::kNullOrUndefined),
                context, reject, UndefinedConstant(), var_exception.value());

            Node* const promise = LoadObjectField(capability, PromiseCapability::kPromiseOffset);
            Return(promise);
        }
    }

} // namespace internal
} // namespace v8
